Skip to content

Commit

Permalink
Add an Arbitrary-friendly expression type for synth-wasm and fuzz
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon committed Nov 3, 2023
1 parent 3d435ef commit c89eb63
Show file tree
Hide file tree
Showing 15 changed files with 627 additions and 68 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 37 additions & 1 deletion soroban-env-common/src/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
extern crate alloc;

use crate::symbol::MAX_SMALL_CHARS;
use crate::xdr::ScError;
use crate::Error;
use crate::{Error, StorageType, Symbol, SymbolSmall, Val, Void};
use arbitrary::{Arbitrary, Unstructured};

impl<'a> Arbitrary<'a> for Error {
Expand All @@ -13,3 +14,38 @@ impl<'a> Arbitrary<'a> for Error {
Ok(error)
}
}

impl<'a> Arbitrary<'a> for Void {
fn arbitrary(_u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Val::VOID)
}
}

impl<'a> Arbitrary<'a> for Symbol {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let len: usize = u.int_in_range(0..=MAX_SMALL_CHARS)?;
let mut buf = [0u8; MAX_SMALL_CHARS];
for i in 0..len {
buf[i] = (*u.choose(&[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_',
])?) as u8;
}
let small =
SymbolSmall::try_from(&buf[0..len]).map_err(|_| arbitrary::Error::IncorrectFormat)?;
Ok(small.into())
}
}

impl<'a> Arbitrary<'a> for StorageType {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let st = match u.int_in_range(0..=2)? {
0 => StorageType::Instance,
1 => StorageType::Persistent,
_ => StorageType::Temporary,
};
Ok(st)
}
}
2 changes: 1 addition & 1 deletion soroban-env-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ linregress = "0.5.1"
pretty_assertions = "1.4.0"

[features]
testutils = ["soroban-env-common/testutils", "recording_auth"]
testutils = ["soroban-env-common/testutils", "soroban-synth-wasm/testutils", "recording_auth"]
next = ["soroban-env-common/next", "soroban-test-wasms/next", "soroban-synth-wasm/next", "soroban-bench-utils/next"]
tracy = ["dep:tracy-client", "soroban-env-common/tracy"]
recording_auth = []
Expand Down
1 change: 1 addition & 0 deletions soroban-env-host/fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions soroban-env-host/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ name = "fuzz_target_1"
path = "fuzz_targets/fuzz_target_1.rs"
test = false
doc = false

[[bin]]
name = "expr"
path = "fuzz_targets/expr.rs"
test = false
doc = false
33 changes: 33 additions & 0 deletions soroban-env-host/fuzz/fuzz_targets/expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#![no_main]

use soroban_env_host::{
budget::AsBudget,
xdr::{ScErrorCode, ScErrorType},
Env, Host, Symbol,
};
use soroban_synth_wasm::{Emit, ValExpr};

use libfuzzer_sys::fuzz_target;

fuzz_target!(|expr: ValExpr| {
// fuzzed code goes here
let wasm = expr.as_single_function_wasm_module("test");
let host = Host::test_host_with_recording_footprint();
host.enable_debug().unwrap();
let contract_id_obj = host.register_test_contract_wasm(wasm.as_slice());
host.as_budget().reset_unlimited().unwrap();
let res = host.call(
contract_id_obj,
Symbol::try_from_small_str("test").unwrap(),
host.test_vec_obj::<u32>(&[]).unwrap(),
);
// Non-internal error-code returns are ok, we are interested in _panics_ and
// internal errors.
if let Err(hosterror) = res {
if hosterror.error.is_code(ScErrorCode::InternalError)
&& !hosterror.error.is_type(ScErrorType::Contract)
{
panic!("got internal error: {:?}", hosterror)
}
}
});
2 changes: 1 addition & 1 deletion soroban-env-host/src/testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl SnapshotSource for MockSnapshotSource {
}

impl Host {
pub const TEST_PRNG_SEED: &[u8; 32] = b"12345678901234567890123456789012";
pub const TEST_PRNG_SEED: &'static [u8; 32] = b"12345678901234567890123456789012";

pub fn test_host() -> Self {
let host = Host::default();
Expand Down
49 changes: 1 addition & 48 deletions soroban-env-macros/src/call_macro_with_all_host_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,13 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::{
collections::{btree_map::Entry, BTreeMap},
fs::File,
iter,
};
use syn::{Error, LitStr};

use serde::{Deserialize, Serialize};

use crate::path;

pub fn generate(file_lit: LitStr) -> Result<TokenStream, Error> {
let file_str = file_lit.value();
let file_path = path::abs_from_rel_to_manifest(&file_str);

let file = File::open(&file_path).map_err(|e| {
Error::new(
file_lit.span(),
format!("error reading file '{file_str}': {e}"),
)
})?;

let root: Root = serde_json::from_reader(file).map_err(|e| {
Error::new(
file_lit.span(),
format!("error parsing file '{file_str}': {e}"),
)
})?;
let root: crate::Root = crate::load_env_file(file_lit.clone())?;

let mut export_names = BTreeMap::<String, String>::new();
for m in root.modules.iter() {
Expand Down Expand Up @@ -153,31 +134,3 @@ pub fn generate(file_lit: LitStr) -> Result<TokenStream, Error> {
pub use _call_macro_with_all_host_functions as call_macro_with_all_host_functions;
})
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Root {
pub modules: Vec<Module>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Module {
pub name: String,
pub export: String,
pub functions: Vec<Function>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Function {
pub export: String,
pub name: String,
pub args: Vec<Arg>,
pub r#return: String,
pub docs: Option<String>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Arg {
pub name: String,
pub r#type: String,
}
58 changes: 58 additions & 0 deletions soroban-env-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod call_macro_with_all_host_functions;
mod path;
mod synth_wasm_expr_type;
use serde::{Deserialize, Serialize};

extern crate proc_macro;

Expand Down Expand Up @@ -75,6 +77,53 @@ impl ToTokens for MetaConstsOutput {
}
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct Root {
pub(crate) modules: Vec<Module>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct Module {
pub(crate) name: String,
pub(crate) export: String,
pub(crate) functions: Vec<Function>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct Function {
pub(crate) export: String,
pub(crate) name: String,
pub(crate) args: Vec<Arg>,
pub(crate) r#return: String,
pub(crate) docs: Option<String>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Arg {
pub(crate) name: String,
pub(crate) r#type: String,
}

fn load_env_file(file_lit: LitStr) -> Result<Root, syn::Error> {
let file_str = file_lit.value();
let file_path = path::abs_from_rel_to_manifest(&file_str);

let file = std::fs::File::open(&file_path).map_err(|e| {
syn::Error::new(
file_lit.span(),
format!("error reading file '{file_str}': {e}"),
)
})?;

serde_json::from_reader(file).map_err(|e| {
syn::Error::new(
file_lit.span(),
format!("error parsing file '{file_str}': {e}"),
)
})
}

#[proc_macro]
pub fn generate_env_meta_consts(input: TokenStream) -> TokenStream {
let meta_input = parse_macro_input!(input as MetaInput);
Expand All @@ -90,3 +139,12 @@ pub fn generate_call_macro_with_all_host_functions(input: TokenStream) -> TokenS
Err(e) => e.to_compile_error().into(),
}
}

#[proc_macro]
pub fn generate_synth_wasm_expr_type(input: TokenStream) -> TokenStream {
let file = parse_macro_input!(input as LitStr);
match synth_wasm_expr_type::generate(file) {
Ok(t) => t.into(),
Err(e) => e.to_compile_error().into(),
}
}
Loading

0 comments on commit c89eb63

Please sign in to comment.