diff --git a/crates/movy-analysis/src/type_graph.rs b/crates/movy-analysis/src/type_graph.rs index e5bbfa2..70e00ca 100644 --- a/crates/movy-analysis/src/type_graph.rs +++ b/crates/movy-analysis/src/type_graph.rs @@ -4,7 +4,8 @@ use std::{ }; use movy_types::abi::{ - MoveAbiSignatureToken, MoveFunctionAbi, MoveModuleAbi, MoveModuleId, MovePackageAbi, + MoveAbiSignatureToken, MoveFunctionAbi, MoveFunctionVisibility, MoveModuleAbi, MoveModuleId, + MovePackageAbi, }; use petgraph::{graph::NodeIndex, visit::EdgeRef}; use serde::{Deserialize, Serialize}; @@ -126,6 +127,7 @@ impl MoveTypeGraph { pub fn find_consumers( &self, ty: &MoveAbiSignatureToken, + public_only: bool, ) -> Vec<(&MoveModuleId, &MoveFunctionAbi)> { let mut consumers = vec![]; for (graph_ty, node) in self.tys.iter() { @@ -137,6 +139,9 @@ impl MoveTypeGraph { .edges_directed(*node, petgraph::Direction::Outgoing) { if let TypeGraphNode::Function(m, f) = &self.graph[edge.target()] { + if public_only && f.visibility != MoveFunctionVisibility::Public { + continue; + } consumers.push((m, f)); } } @@ -148,6 +153,7 @@ impl MoveTypeGraph { pub fn find_producers( &self, ty: &MoveAbiSignatureToken, + public_only: bool, ) -> Vec<(MoveModuleId, MoveFunctionAbi)> { let mut producers = vec![]; for (graph_ty, node) in self.tys.iter() { @@ -159,6 +165,9 @@ impl MoveTypeGraph { .edges_directed(*node, petgraph::Direction::Incoming) { if let TypeGraphNode::Function(m, f) = &self.graph[edge.source()] { + if public_only && f.visibility != MoveFunctionVisibility::Public { + continue; + } producers.push((m.clone(), f.clone())); } } diff --git a/crates/movy-fuzz/src/executor.rs b/crates/movy-fuzz/src/executor.rs index 65ac69f..47faa3a 100644 --- a/crates/movy-fuzz/src/executor.rs +++ b/crates/movy-fuzz/src/executor.rs @@ -148,8 +148,18 @@ where .expect("tracer should be present when tracing is enabled") .outcome(); + trace!("Execution finished with status: {:?}", effects.status()); + let (stage_idx, success) = match effects.status() { - ExecutionStatus::Failure { command, .. } => (command.map(|c| c), false), + ExecutionStatus::Failure { command, .. } => ( + // command index may be out of bound when meeting non-aborted error + if command.is_some_and(|c| c < input.sequence().commands.len()) { + command.clone() + } else { + None + }, + false, + ), _ => (None, true), }; if effects.status().is_err() { diff --git a/crates/movy-fuzz/src/meta.rs b/crates/movy-fuzz/src/meta.rs index 8a02cd7..ad365d3 100644 --- a/crates/movy-fuzz/src/meta.rs +++ b/crates/movy-fuzz/src/meta.rs @@ -216,7 +216,7 @@ fn should_skip_function(base: &Metadata, func_data: &MoveFunctionAbi) -> bool { MoveAbiSignatureToken::Reference(_) | MoveAbiSignatureToken::MutableReference(_) ); let hanging_hot_potato = - ret_ty.is_hot_potato() && base.type_graph.find_consumers(ret_ty).is_empty(); + ret_ty.is_hot_potato() && base.type_graph.find_consumers(ret_ty, true).is_empty(); if self_used || ret_ref || hanging_hot_potato { return true; } diff --git a/crates/movy-fuzz/src/mutators/object_data.rs b/crates/movy-fuzz/src/mutators/object_data.rs index 485d597..5337c67 100644 --- a/crates/movy-fuzz/src/mutators/object_data.rs +++ b/crates/movy-fuzz/src/mutators/object_data.rs @@ -184,6 +184,7 @@ impl ObjectData { // continue; // Skip parameters that have the Copy ability // } let instantiated_param = param.subst(ty_args_map).unwrap(); + let partial_instantiation = param.partial_subst(ty_args_map); existing_objects .get_mut(&instantiated_param) .unwrap_or_else(|| { @@ -203,25 +204,26 @@ impl ObjectData { if matches!(arg, SequenceArgument::Input(_)) { continue; // Skip input arguments for key store and hot potatoes } - if param.is_balance() { + if partial_instantiation.is_balance() { balances.retain(|a| *a != *arg); // Remove from balances if it is a balance object } - if param.is_key_store() { + if partial_instantiation.is_key_store() { key_store_objects.retain(|a| *a != *arg); // Remove from key_store_objects if it is a key store object } - if param.is_hot_potato() { + if partial_instantiation.is_hot_potato() { hot_potatoes.remove( hot_potatoes .iter() .position(|x| x == &instantiated_param) .unwrap_or_else(|| { panic!( - "Expected hot potato object for type {:?}, input {}", + "Expected hot potato object for type {}, input {}, hot potatoes {:?}", instantiated_param, MoveFuzzInput { sequence: ptb.clone(), ..Default::default() - } + }, + hot_potatoes ) }), ); @@ -265,6 +267,7 @@ impl ObjectData { continue; } let instantiated_ret_ty = ret_ty.subst(ty_args_map).unwrap(); + let partial_instantiation = ret_ty.partial_subst(ty_args_map); if !matches!(instantiated_ret_ty, MoveTypeTag::Struct(_)) { continue; // Only process struct return types } @@ -277,13 +280,13 @@ impl ObjectData { .entry(instantiated_ret_ty.clone()) .or_default() .push((res_arg, Gate::Owned)); - if ret_ty.is_balance() { + if partial_instantiation.is_balance() { balances.push(res_arg); } - if ret_ty.is_key_store() { + if partial_instantiation.is_key_store() { key_store_objects.push(res_arg); } - if ret_ty.is_hot_potato() { + if partial_instantiation.is_hot_potato() { hot_potatoes.push(instantiated_ret_ty); } } @@ -899,7 +902,7 @@ where .flat_map(|type_tag| { meta_state .type_graph - .find_consumers(&MoveAbiSignatureToken::from_type_tag_lossy(type_tag)) + .find_consumers(&MoveAbiSignatureToken::from_type_tag_lossy(type_tag), true) .iter() .map(|(module_id, consumer_function)| { FunctionIdent::new( diff --git a/crates/movy-fuzz/src/mutators/sequence/append.rs b/crates/movy-fuzz/src/mutators/sequence/append.rs index 38ad8f7..ad4b8a5 100644 --- a/crates/movy-fuzz/src/mutators/sequence/append.rs +++ b/crates/movy-fuzz/src/mutators/sequence/append.rs @@ -209,7 +209,10 @@ where } let arg_type = struct_params.remove(0).partial_subst(&ty_args); - let funcs = state.fuzz_state().type_graph.find_producers(&arg_type); + let funcs = state + .fuzz_state() + .type_graph + .find_producers(&arg_type, true); // except itself let funcs = funcs .iter() diff --git a/crates/movy-fuzz/src/mutators/utils.rs b/crates/movy-fuzz/src/mutators/utils.rs index f42edbf..2f1801b 100644 --- a/crates/movy-fuzz/src/mutators/utils.rs +++ b/crates/movy-fuzz/src/mutators/utils.rs @@ -83,6 +83,10 @@ impl StageReplay { self.reset_progress(); return StageReplayAction::Fresh; }; + debug!( + "StageReplay decide: current stage idx {}, recorded stage idx {:?}, attempts {}", + stage_idx, self.stage_idx, self.attempts + ); let prev_stage = self.stage_idx; self.stage_idx = match self.stage_idx { @@ -266,7 +270,7 @@ where let mut ty_args = if let Some(stage_idx) = stage_idx { let Some(cmd) = ptb.commands.get_mut(*stage_idx) else { warn!( - "Stage idx {} out of bounds for commands: {:?}", + "Stage idx {} out of bounds for commands mutating ty args: {:?}", stage_idx, ptb.commands ); return MutationResult::Skipped; diff --git a/crates/movy-replay/src/tracer/concolic.rs b/crates/movy-replay/src/tracer/concolic.rs index c3d7ebc..ba92064 100644 --- a/crates/movy-replay/src/tracer/concolic.rs +++ b/crates/movy-replay/src/tracer/concolic.rs @@ -316,7 +316,7 @@ impl ConcolicState { self.stack.pop(); } if self.stack.len() != s.value.len() { - debug!( + warn!( "stack: {:?}, stack from trace: {:?}, event: {:?}, disabling concolic execution", self.stack, s.value, event ); @@ -423,6 +423,13 @@ impl ConcolicState { instruction, extra, } => { + trace!( + "Before instruction at pc {}: {:?}, extra: {:?}. Current stack: {:?}", + pc, + instruction, + extra, + stack.map(|s| &s.value) + ); match instruction { Bytecode::Pop | Bytecode::BrTrue(_) @@ -1003,11 +1010,14 @@ impl ConcolicState { _ => unreachable!(), } } + Bytecode::VariantSwitch(_) => { + self.stack.pop(); + } _ => {} } } _ => { - trace!("Unsupported event: {:?}", event); + // trace!("Unsupported event: {:?}", event); } } None