IR fuzz target with Arbitrary implementations

This commit is contained in:
Dzmitry Malyshau
2022-01-13 13:26:21 -05:00
parent 9f37624592
commit 58b4fd0f57
8 changed files with 106 additions and 7 deletions

View File

@@ -14,10 +14,12 @@ resolver = "2"
[package.metadata.docs.rs]
all-features = true
[dependencies]
# MSRV warnings:
# - bitflags 1.3 requires Rust-1.46
# - indexmap 1.7 requires Rust-1.49
[dependencies]
arbitrary = { version = "1", features = ["derive"], optional = true }
bitflags = "1"
bit-set = "0.5"
codespan-reporting = { version = "0.11.0", optional = true }

View File

@@ -14,7 +14,7 @@ libfuzzer-sys = "0.4"
[dependencies.naga]
path = ".."
features = ["spv-in", "wgsl-in", "glsl-in"]
features = ["arbitrary", "spv-in", "wgsl-in", "glsl-in", "validate"]
# Prevent this from interfering with workspaces
[workspace]
@@ -37,3 +37,9 @@ name = "glsl_parser"
path = "fuzz_targets/glsl_parser.rs"
test = false
doc = false
[[bin]]
name = "ir"
path = "fuzz_targets/ir.rs"
test = false
doc = false

10
fuzz/fuzz_targets/ir.rs Normal file
View File

@@ -0,0 +1,10 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|module: naga::Module| {
use naga::valid as v;
// Check if the module validates without errors.
//TODO: may also fuzz the flags and capabilities
let mut validator = v::Validator::new(v::ValidationFlags::all(), v::Capabilities::empty());
let _result = validator.validate(&module);
});

View File

@@ -1,6 +1,6 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use naga::front::spv::{Parser, Options};
use naga::front::spv::{Options, Parser};
fuzz_target!(|data: Vec<u32>| {
// Ensure the parser can handle potentially malformed data without crashing.

View File

@@ -17,6 +17,7 @@ use indexmap::set::IndexSet;
any(feature = "serialize", feature = "deserialize"),
serde(transparent)
)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Handle<T> {
index: Index,
#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))]
@@ -110,6 +111,7 @@ impl<T> Handle<T> {
any(feature = "serialize", feature = "deserialize"),
serde(transparent)
)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Range<T> {
inner: ops::Range<u32>,
#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))]
@@ -154,6 +156,7 @@ impl<T> Iterator for Range<T> {
/// a reference to the stored item.
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(transparent))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(test, derive(PartialEq))]
pub struct Arena<T> {
/// Values of this arena.
@@ -543,8 +546,7 @@ impl<T> ops::Index<Handle<T>> for UniqueArena<T> {
#[cfg(feature = "serialize")]
impl<T> serde::Serialize for UniqueArena<T>
where
T: Eq + hash::Hash,
T: serde::Serialize,
T: Eq + hash::Hash + serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@@ -557,8 +559,7 @@ where
#[cfg(feature = "deserialize")]
impl<'de, T> serde::Deserialize<'de> for UniqueArena<T>
where
T: Eq + hash::Hash,
T: serde::Deserialize<'de>,
T: Eq + hash::Hash + serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -575,3 +576,36 @@ where
})
}
}
//Note: largely borrowed from `HashSet` implementation
#[cfg(feature = "arbitrary")]
impl<'a, T> arbitrary::Arbitrary<'a> for UniqueArena<T>
where
T: Eq + hash::Hash + arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut arena = Self::default();
for elem in u.arbitrary_iter()? {
arena.set.insert(elem?);
#[cfg(feature = "span")]
arena.span_info.push(Span::UNDEFINED);
}
Ok(arena)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut arena = Self::default();
for elem in u.arbitrary_take_rest_iter()? {
arena.set.insert(elem?);
#[cfg(feature = "span")]
arena.span_info.push(Span::UNDEFINED);
}
Ok(arena)
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
let depth_hint = <usize as arbitrary::Arbitrary>::size_hint(depth);
arbitrary::size_hint::and(depth_hint, (0, None))
}
}

View File

@@ -5,6 +5,7 @@ use std::ops::{Deref, DerefMut, RangeBounds};
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(transparent))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Block {
body: Vec<Statement>,
#[cfg(feature = "span")]

View File

@@ -217,6 +217,8 @@ pub mod valid;
pub use crate::arena::{Arena, Handle, Range, UniqueArena};
pub use crate::span::{Span, SpanContext, WithSpan};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[cfg(feature = "deserialize")]
use serde::Deserialize;
#[cfg(feature = "serialize")]
@@ -248,6 +250,7 @@ pub(crate) type NamedExpressions = FastHashMap<Handle<Expression>, String>;
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct EarlyDepthTest {
conservative: Option<ConservativeDepth>,
}
@@ -264,6 +267,7 @@ pub struct EarlyDepthTest {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ConservativeDepth {
/// Shader may rewrite depth only with a value greater than calculated;
GreaterEqual,
@@ -279,6 +283,7 @@ pub enum ConservativeDepth {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[allow(missing_docs)] // The names are self evident
pub enum ShaderStage {
Vertex,
@@ -290,6 +295,7 @@ pub enum ShaderStage {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum StorageClass {
/// Function locals.
Function,
@@ -311,6 +317,7 @@ pub enum StorageClass {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum BuiltIn {
Position,
ViewIndex,
@@ -345,6 +352,7 @@ pub type Bytes = u8;
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum VectorSize {
/// 2D vector
Bi = 2,
@@ -359,6 +367,7 @@ pub enum VectorSize {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ScalarKind {
/// Signed integer type.
Sint,
@@ -375,6 +384,7 @@ pub enum ScalarKind {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ArraySize {
/// The array size is constant.
Constant(Handle<Constant>),
@@ -386,6 +396,7 @@ pub enum ArraySize {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum Interpolation {
/// The value will be interpolated in a perspective-correct fashion.
/// Also known as "smooth" in glsl.
@@ -402,6 +413,7 @@ pub enum Interpolation {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum Sampling {
/// Interpolate the value at the center of the pixel.
Center,
@@ -421,6 +433,7 @@ pub enum Sampling {
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct StructMember {
pub name: Option<String>,
/// Type of the field.
@@ -435,6 +448,7 @@ pub struct StructMember {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ImageDimension {
/// 1D image
D1,
@@ -450,6 +464,7 @@ bitflags::bitflags! {
/// Flags describing an image.
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[derive(Default)]
pub struct StorageAccess: u32 {
/// Storage can be used as a source for load ops.
@@ -463,6 +478,7 @@ bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum StorageFormat {
// 8-bit formats
R8Unorm,
@@ -513,6 +529,7 @@ pub enum StorageFormat {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ImageClass {
/// Regular sampled image.
Sampled {
@@ -540,6 +557,7 @@ pub enum ImageClass {
#[derive(Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct Type {
/// The name of the type, if any.
pub name: Option<String>,
@@ -551,6 +569,7 @@ pub struct Type {
#[derive(Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum TypeInner {
/// Number of integral or floating-point kind.
Scalar { kind: ScalarKind, width: Bytes },
@@ -680,6 +699,7 @@ pub enum TypeInner {
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct Constant {
pub name: Option<String>,
pub specialization: Option<u32>,
@@ -690,6 +710,7 @@ pub struct Constant {
#[derive(Debug, Clone, Copy, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ScalarValue {
Sint(i64),
Uint(u64),
@@ -701,6 +722,7 @@ pub enum ScalarValue {
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ConstantInner {
Scalar {
width: Bytes,
@@ -716,6 +738,7 @@ pub enum ConstantInner {
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum Binding {
/// Built-in shader variable.
BuiltIn(BuiltIn),
@@ -747,6 +770,7 @@ pub enum Binding {
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct ResourceBinding {
/// The bind group index.
pub group: u32,
@@ -758,6 +782,7 @@ pub struct ResourceBinding {
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct GlobalVariable {
/// Name of the variable, if any.
pub name: Option<String>,
@@ -775,6 +800,7 @@ pub struct GlobalVariable {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct LocalVariable {
/// Name of the variable, if any.
pub name: Option<String>,
@@ -788,6 +814,7 @@ pub struct LocalVariable {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum UnaryOperator {
Negate,
Not,
@@ -797,6 +824,7 @@ pub enum UnaryOperator {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum BinaryOperator {
Add,
Subtract,
@@ -827,6 +855,7 @@ pub enum BinaryOperator {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum AtomicFunction {
Add,
Subtract,
@@ -842,6 +871,7 @@ pub enum AtomicFunction {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum DerivativeAxis {
X,
Y,
@@ -852,6 +882,7 @@ pub enum DerivativeAxis {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum RelationalFunction {
All,
Any,
@@ -865,6 +896,7 @@ pub enum RelationalFunction {
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum MathFunction {
// comparison
Abs,
@@ -948,6 +980,7 @@ pub enum MathFunction {
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum SampleLevel {
Auto,
Zero,
@@ -963,6 +996,7 @@ pub enum SampleLevel {
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum ImageQuery {
/// Get the size at the specified level.
Size {
@@ -982,6 +1016,7 @@ pub enum ImageQuery {
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum SwizzleComponent {
///
X = 0,
@@ -997,6 +1032,7 @@ bitflags::bitflags! {
/// Memory barrier flags.
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[derive(Default)]
pub struct Barrier: u32 {
/// Barrier affects all `StorageClass::Storage` accesses.
@@ -1013,6 +1049,7 @@ bitflags::bitflags! {
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum Expression {
/// Array access with a computed index.
///
@@ -1301,6 +1338,7 @@ pub use block::Block;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum SwitchValue {
Integer(i32),
Default,
@@ -1311,6 +1349,7 @@ pub enum SwitchValue {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct SwitchCase {
/// Value, upon which the case is considered true.
pub value: SwitchValue,
@@ -1327,6 +1366,7 @@ pub struct SwitchCase {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum Statement {
/// Emit a range of expressions, visible to all statements that follow in this block.
///
@@ -1470,6 +1510,7 @@ pub enum Statement {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct FunctionArgument {
/// Name of the argument, if any.
pub name: Option<String>,
@@ -1483,6 +1524,7 @@ pub struct FunctionArgument {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct FunctionResult {
/// Type of the result.
pub ty: Handle<Type>,
@@ -1495,6 +1537,7 @@ pub struct FunctionResult {
#[derive(Debug, Default)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct Function {
/// Name of the function, if any.
pub name: Option<String>,
@@ -1558,6 +1601,7 @@ pub struct Function {
#[derive(Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct EntryPoint {
/// Name of this entry point, visible externally.
///
@@ -1587,6 +1631,7 @@ pub struct EntryPoint {
#[derive(Debug, Default)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct Module {
/// Storage for the types defined in this module.
pub types: UniqueArena<Type>,

View File

@@ -3,6 +3,7 @@ use std::{error::Error, fmt, ops::Range};
/// A source code span, used for error reporting.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Span {
start: u32,
end: u32,