Skip to content

Add intial porting of table_ops from arbitrary to mutatis #10924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 27, 2025

Conversation

khagankhan
Copy link
Contributor

@khagankhan khagankhan commented Jun 4, 2025

This is the initial (unofficial) porting of table_ops from arbitrary to mutatis.

No new features are added. This is a direct port of the existing implementation using the arbitrary crate to the mutatis framework.

Open to review and feedback.

Initial work towards: #10327
cc @ospencer @fitzgen

@khagankhan khagankhan requested a review from a team as a code owner June 4, 2025 19:38
@khagankhan khagankhan requested review from alexcrichton and removed request for a team June 4, 2025 19:38
@fitzgen fitzgen requested review from fitzgen and removed request for alexcrichton June 4, 2025 20:03
@github-actions github-actions bot added the fuzzing Issues related to our fuzzing infrastructure label Jun 4, 2025
Copy link

github-actions bot commented Jun 4, 2025

Subscribe to Label Action

cc @fitzgen

This issue or pull request has been labeled: "fuzzing"

Thus the following users have been cc'd because of the following labels:

  • fitzgen: fuzzing

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on the right track!

You can rebase on main to get the cargo vet audits for mutatis so that that part of CI stops failing.

Also make sure to check that the actual fuzz target that runs this stuff continues to build via

$ cargo fuzz check --no-default-features -s none misc

You probably need to fix some stuff up in there and maybe hook up a custom mutator for libfuzzer, as described here: https://docs.rs/mutatis/latest/mutatis/_guide/fuzzer_integration/index.html

Comment on lines 137 to 151
/// Computes the abstract stack depth after executing all operations
pub fn abstract_stack_depth(&self) -> usize {
let mut stack: usize = 0;
for op in &self.ops {
let pop = op.operands_len();
let push = op.results_len();
stack = stack.saturating_sub(pop);
stack += push;
}
stack
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we won't need this helper anymore, given the fixup helper function I suggested in private communication.

Comment on lines 156 to 234
// 1. Append
c.mutation(|ctx| {
let mut stack = ops.abstract_stack_depth();
add_table_op_mutatis(ops, ctx, &mut stack)
})?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this can become an arbitrary insert instead of append (which is a subset of an insert operation).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the insert mutation would be something like

c.mutation(|ctx| {
    let i = ctx.rng().gen_index(ops.ops.len() + 1).unwrap();
    ops.ops.insert(random_op());
    Ok(())
})?;

Comment on lines 196 to 197
// 3. Replace
c.mutation(|ctx| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I think this function body will get cleaned up a ton with the fixup pass. Additionally, whenever possible we don't want to report mutations that we know will fail so if the ops are empty we shouldn't call c.mutation(...). Putting these things together, I expect this will become something like:

if !ops.ops.is_empty() {
    c.mutation(|ctx| {
        let i = ctx.rng().gen_index(ops.ops.len()).unwrap();
        ops.ops[i] = choose_random_op();
        Ok(())
    })?;
}

Comment on lines 161 to 195
// 2. Remove
c.mutation(|ctx| {
if ops.ops.is_empty() {
return Ok(());
}
if let Some(idx) = ctx.rng().gen_index(ops.ops.len()) {
let removed = ops.ops.remove(idx);
log::debug!("Remove called at idx: {} with opcode {:?}", idx, removed);
let mut stack = 0isize;
for op in &ops.ops[..idx] {
stack += op.results_len() as isize;
stack -= op.operands_len() as isize;
}
stack -= removed.results_len() as isize;
stack += removed.operands_len() as isize;

log::debug!(
"op = {:?}, pop = {}, push = {}, resulting stack = {}",
removed,
removed.operands_len(),
removed.results_len(),
stack
);
for _ in 0..removed.results_len() {
ops.ops.insert(idx, TableOp::Null());
log::debug!(
"Patched with ref.null at idx {} after removing {:?}",
idx,
removed
);
stack += 1;
}
}
Ok(())
})?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this can become something like

if !ops.ops.is_empty() {
    c.mutation(|ctx| {
        let i = ctx.rng().gen_index(ops.ops.len()).unwrap();
        ops.ops.remove(i);
        Ok(())
    })?;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I am working on that and a fixup method results in much cleaner code.

};
while ops.len() < MAX_OPS {
temp_ops.ops = ops.clone();
let mut stack = temp_ops.abstract_stack_depth();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is accidentally O(n*n) because the helper walks every op in every iteration of this loop. You'll want to have a local stack variable that is adjusted as ops are generated here.

Comment on lines 523 to 531
macro_rules! empty_table_ops {
($num_params:expr, $num_globals:expr, $table_size:expr) => {
TableOps {
num_params: $num_params,
num_globals: $num_globals,
table_size: $table_size,
ops: vec![],
}
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This macro should just be TableOps::default(), which can simply be derived on the type rather than written out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added new empty() method

use std::ops::RangeInclusive;
use wasm_encoder::{
CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection,
GlobalSection, ImportSection, Instruction, Module, RefType, TableSection, TableType,
TypeSection, ValType,
};

/// A description of a Wasm module that makes a series of `externref` table
/// operations.
#[derive(Debug)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we would add Default next to Debug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 504 to 465
macro_rules! default_table_ops {
($num_params:expr, $num_globals:expr, $table_size:expr) => {
TableOps {
num_params: $num_params,
num_globals: $num_globals,
table_size: $table_size,
ops: vec![
TableOp::Null(),
TableOp::Drop(),
TableOp::Gc(),
TableOp::LocalSet(0),
TableOp::LocalGet(0),
TableOp::GlobalSet(0),
TableOp::GlobalGet(0),
],
}
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should need this, and can simply use an empty TableOps::default() instead, but if we did want to create this particular sequence in a bunch of places we should just use a function rather than a macro.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added new methods instead of macros

Comment on lines 558 to 566
#[test]
fn test_tableops_mutate_with() -> mutatis::Result<()> {
let _ = env_logger::try_init();
let mut res = default_table_ops![5, 5, 5];
let mut generator = TableOpsMutator;
let mut session = mutatis::Session::new();

for _ in 0..=1024 {
session.mutate_with(&mut generator, &mut res)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the TableOpsMutator is the default, we don't actually need this test; it is redundant with mutate_table_ops_with_default_mutator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

assert!(unseen_ops.is_empty(), "Failed to generate {unseen_ops:?}");
Ok(())
}
// TODO: Write a test for expected wat
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder for this TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@khagankhan
Copy link
Contributor Author

khagankhan commented Jun 12, 2025

Thanks, @fitzgen! currently working on it. Will integrate your suggestions in the next commit.

@khagankhan khagankhan requested a review from a team as a code owner June 27, 2025 12:33
@khagankhan khagankhan requested review from alexcrichton and removed request for a team June 27, 2025 12:33
@alexcrichton alexcrichton requested review from fitzgen and removed request for alexcrichton June 27, 2025 14:28
Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks!

@fitzgen fitzgen added this pull request to the merge queue Jun 27, 2025
Merged via the queue into bytecodealliance:main with commit 02eb659 Jun 27, 2025
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fuzzing Issues related to our fuzzing infrastructure
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants