introduce libFuzzer

This commit is contained in:
Levy A. 2025-03-12 11:29:37 -03:00
parent 63630ff956
commit 269857d66c
5 changed files with 1827 additions and 2 deletions

View file

@ -17,10 +17,10 @@
overlays = [ (import rust-overlay) ];
};
toolchain = break ((pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
extensions = [ "rust-analyzer" "rust-src" ];
targets = [ "wasm32-unknown-unknown" ];
});
};
lib = pkgs.lib;
@ -68,6 +68,13 @@
apple-sdk
];
};
devShells.fuzz = with pkgs; mkShell {
nativeBuildInputs = [
(pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal))
] ++ lib.optionals pkgs.stdenv.isDarwin [
apple-sdk
];
};
}
);
}

3
fuzz/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
target
corpus
artifacts

1659
fuzz/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

23
fuzz/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "limbo-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1.4.1", features = ["derive"] }
limbo_core = { path = "../core" }
rusqlite = "0.34.0"
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "expression"
path = "fuzz_targets/expression.rs"

View file

@ -0,0 +1,133 @@
#![no_main]
use core::fmt;
use std::{num::NonZero, sync::Arc};
use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;
use limbo_core::{OwnedValue, IO as _};
#[derive(Debug, Arbitrary)]
enum Binary {
Equal,
NotEqual,
}
impl fmt::Display for Binary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Binary::Equal => "=",
Binary::NotEqual => "<>",
}
)
}
}
#[derive(Debug, Arbitrary, Clone)]
enum Value {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
}
impl From<Value> for limbo_core::OwnedValue {
fn from(value: Value) -> limbo_core::OwnedValue {
match value {
Value::Null => limbo_core::OwnedValue::Null,
Value::Integer(v) => limbo_core::OwnedValue::Integer(v),
Value::Real(v) => limbo_core::OwnedValue::Float(v),
Value::Text(v) => limbo_core::OwnedValue::from_text(&v),
Value::Blob(v) => limbo_core::OwnedValue::from_blob(v.to_owned()),
}
}
}
impl rusqlite::ToSql for Value {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
use rusqlite::types::ToSqlOutput;
Ok(match self {
Value::Null => ToSqlOutput::Owned(rusqlite::types::Value::Null),
Value::Integer(v) => ToSqlOutput::Owned(rusqlite::types::Value::Integer(*v)),
Value::Real(v) => ToSqlOutput::Owned(rusqlite::types::Value::Real(*v)),
Value::Text(v) => ToSqlOutput::Owned(rusqlite::types::Value::Text(v.to_owned())),
Value::Blob(v) => ToSqlOutput::Owned(rusqlite::types::Value::Blob(v.to_owned())),
})
}
}
impl rusqlite::types::FromSql for Value {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
Ok(match value {
rusqlite::types::ValueRef::Null => Value::Null,
rusqlite::types::ValueRef::Integer(v) => Value::Integer(v),
rusqlite::types::ValueRef::Real(v) => Value::Real(v),
rusqlite::types::ValueRef::Text(v) => {
Value::Text(String::from_utf8_lossy(v).to_string())
}
rusqlite::types::ValueRef::Blob(v) => Value::Blob(v.to_vec()),
})
}
}
#[derive(Debug, Arbitrary)]
enum Expr {
Value(Value),
Binary(Binary, Box<Expr>, Box<Expr>),
}
fn to_sql(expr: &Expr) -> (String, Vec<Value>) {
match expr {
Expr::Value(value) => ("?".to_string(), vec![value.clone()]),
Expr::Binary(op, lhs, rhs) => {
let mut lhs = to_sql(lhs);
let mut rhs = to_sql(rhs);
lhs.1.append(&mut rhs.1);
(format!("({}) {op} ({})", lhs.0, rhs.0), lhs.1)
}
}
}
fn do_fuzz(expr: Expr) {
let (sql, params) = to_sql(&expr);
let sql = format!("select {}", &sql);
let sqlite = rusqlite::Connection::open_in_memory().unwrap();
let io = Arc::new(limbo_core::MemoryIO::new());
let memory = limbo_core::Database::open_file(io.clone(), ":memory:", true).unwrap();
let limbo = memory.connect().unwrap();
let expected = sqlite
.query_row(&sql, rusqlite::params_from_iter(params.iter()), |row| {
row.get::<_, Value>(0)
})
.unwrap();
let mut stmt = limbo.prepare(sql).unwrap();
for (idx, value) in params.into_iter().enumerate() {
stmt.bind_at(NonZero::new(idx + 1).unwrap(), value.into())
}
let value = 'value: {
loop {
use limbo_core::StepResult;
match stmt.step().unwrap() {
StepResult::IO => io.run_once().unwrap(),
StepResult::Row => {
let row = stmt.row().unwrap();
assert_eq!(row.count(), 1);
break 'value row.get_value(0).clone();
}
_ => unreachable!(),
}
}
};
assert_eq!(OwnedValue::from(expected), value);
}
fuzz_target!(|expr: Expr| do_fuzz(expr));