From e0d813df44ad77614ff48ed356e450f040cc1b19 Mon Sep 17 00:00:00 2001 From: YsielX <332494219@qq.com> Date: Tue, 23 Dec 2025 13:39:17 +0000 Subject: [PATCH 1/2] Fixed replay inconsistency --- crates/movy-fuzz/src/executor.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/crates/movy-fuzz/src/executor.rs b/crates/movy-fuzz/src/executor.rs index 074d8d4..bc74422 100644 --- a/crates/movy-fuzz/src/executor.rs +++ b/crates/movy-fuzz/src/executor.rs @@ -160,6 +160,26 @@ where .expect("no code ob installed"); code_ob.reset_map()?; } + let extra = ExecutionExtraOutcome { + logs: trace_outcome.logs, + solver: trace_outcome.concolic, + stage_idx, + success, + }; + let events: Vec<_> = events.into_iter().map(|e| e.into()).collect(); + + // Expose preliminary outcome so oracles can inspect events. + let mut exec = ExecutionOutcome { + events_verdict: trace_outcome.verdict, + events: events.clone(), + allowed_success: success, + findings: trace_outcome.findings.clone(), + }; + state.extra_state_mut().global_outcome = Some(GlobalOutcome { + exec: exec.clone(), + extra: extra.clone(), + }); + let oracle_vulns = self.oracles.done_execution(&db, state, &effects)?; if !oracle_vulns.is_empty() { trace_outcome.findings.extend(oracle_vulns.iter().cloned()); @@ -171,15 +191,9 @@ where } else { trace_outcome.verdict }; - let extra = ExecutionExtraOutcome { - logs: trace_outcome.logs, - solver: trace_outcome.concolic, - stage_idx, - success, - }; - let exec = ExecutionOutcome { + exec = ExecutionOutcome { events_verdict: kind, - events: events.into_iter().map(|e| e.into()).collect(), + events, allowed_success: success, findings: trace_outcome.findings.clone(), }; From 009730dbd5476a645960c64a654256ddf64668ce Mon Sep 17 00:00:00 2001 From: YsielX <332494219@qq.com> Date: Tue, 23 Dec 2025 13:40:20 +0000 Subject: [PATCH 2/2] Fixed pre/post argument inconsistency --- crates/movy-fuzz/src/meta.rs | 42 ++-- .../movy-fuzz/src/mutators/sequence/hooks.rs | 224 ++++++++++-------- crates/movy-fuzz/src/utils.rs | 1 + .../src/sui/type_conversion.rs | 12 +- .../src/sui/unchecked_return.rs | 10 +- test-data/counter/tests/movy.move | 2 +- 6 files changed, 159 insertions(+), 132 deletions(-) diff --git a/crates/movy-fuzz/src/meta.rs b/crates/movy-fuzz/src/meta.rs index 7e12487..b8b2ffb 100644 --- a/crates/movy-fuzz/src/meta.rs +++ b/crates/movy-fuzz/src/meta.rs @@ -310,29 +310,35 @@ fn apply_function_filters( let pkg = func.0.module_address; let module_id = &func.0; if let Some(set) = &include_pkgs - && !set.contains(&pkg) { - return false; - } + && !set.contains(&pkg) + { + return false; + } if let Some(set) = &exclude_pkgs - && set.contains(&pkg) { - return false; - } + && set.contains(&pkg) + { + return false; + } if let Some(set) = &include_funcs - && !set.contains(func) { - return false; - } + && !set.contains(func) + { + return false; + } if let Some(set) = &exclude_funcs - && set.contains(func) { - return false; - } + && set.contains(func) + { + return false; + } if let Some(set) = &include_modules - && !set.contains(module_id) { - return false; - } + && !set.contains(module_id) + { + return false; + } if let Some(set) = &exclude_modules - && set.contains(module_id) { - return false; - } + && set.contains(module_id) + { + return false; + } true }) .collect() diff --git a/crates/movy-fuzz/src/mutators/sequence/hooks.rs b/crates/movy-fuzz/src/mutators/sequence/hooks.rs index 44c8e35..40807a1 100644 --- a/crates/movy-fuzz/src/mutators/sequence/hooks.rs +++ b/crates/movy-fuzz/src/mutators/sequence/hooks.rs @@ -104,66 +104,83 @@ fn append_hook_call( let mut fixed_args: BTreeMap = BTreeMap::new(); let mut fixed_ty_args: BTreeMap = BTreeMap::new(); - if let Some(ctx) = ctx_arg - && let Some(param_ty) = hook_abi - .parameters.first() + let param_offset = if ctx_arg.is_some() { 1 } else { 0 }; + if let Some(ctx) = ctx_arg { + if let Some(param_ty) = hook_abi + .parameters + .get(0) .and_then(|p| p.subst(&BTreeMap::new())) { fixed_args.insert(0, (ctx, param_ty)); } - if let Some((target_call, target_abi, maybe_idx)) = target { + } + if let Some((target_call, target_abi, _maybe_idx)) = target { + // Hook must mirror the target function signature (plus optional context). + if hook_abi.type_parameters.len() != target_call.type_arguments.len() { + warn!( + "skip hook {:?} due to mismatched type parameters: hook {} vs target {}", + hook_ident, + hook_abi.type_parameters.len(), + target_call.type_arguments.len() + ); + return; + } + for (i, ty) in target_call.type_arguments.iter().enumerate() { + fixed_ty_args.insert(i as u16, ty.clone()); + } + + if hook_abi.parameters.len() != param_offset + target_abi.parameters.len() { + warn!( + "skip hook {:?} due to mismatched params: hook {} vs target {} (+ctx {})", + hook_ident, + hook_abi.parameters.len(), + target_abi.parameters.len(), + param_offset + ); + return; + } + let ty_args_map = target_call .type_arguments .iter() .enumerate() .map(|(i, ty)| (i as u16, ty.clone())) .collect::>(); - for (i, ty) in target_call.type_arguments.iter().enumerate() { - if i < hook_abi.type_parameters.len() { - fixed_ty_args.insert(i as u16, ty.clone()); - } - } - let mut candidates: Vec<(MoveTypeTag, SequenceArgument)> = target_call - .arguments + let hook_ty_args_map = fixed_ty_args.clone(); + for (i, (hook_param, target_param)) in hook_abi + .parameters .iter() + .skip(param_offset) .zip(target_abi.parameters.iter()) - .filter_map(|(arg, param)| param.subst(&ty_args_map).map(|ty| (ty, *arg))) - .collect(); - if let Some(target_idx) = maybe_idx { - let ret_args = if target_abi.return_paramters.len() == 1 { - vec![SequenceArgument::Result(target_idx)] - } else { - target_abi - .return_paramters - .iter() - .enumerate() - .map(|(i, _)| SequenceArgument::NestedResult(target_idx, i as u16)) - .collect::>() + .enumerate() + { + let Some(hook_ty) = hook_param.subst(&hook_ty_args_map) else { + warn!( + "skip hook {:?} due to unsubstitutable hook param {}", + hook_ident, i + ); + return; }; - candidates.extend( - ret_args - .into_iter() - .zip(target_abi.return_paramters.iter()) - .filter_map(|(arg, ret_ty)| ret_ty.subst(&ty_args_map).map(|ty| (ty, arg))), - ); - } - let mut used = vec![false; candidates.len()]; - let hook_ty_args_map = fixed_ty_args.clone(); - for (idx, param) in hook_abi.parameters.iter().enumerate() { - if ctx_arg.is_some() && idx == 0 { - continue; - } - let Some(param_ty) = param.subst(&hook_ty_args_map) else { - continue; + let Some(target_ty) = target_param.subst(&ty_args_map) else { + warn!( + "skip hook {:?} due to unsubstitutable target param {}", + hook_ident, i + ); + return; }; - if let Some((cand_idx, (_, arg))) = candidates - .iter() - .enumerate() - .find(|(i, (ty, _))| !used[*i] && *ty == param_ty) - { - fixed_args.insert(idx as u16, (*arg, param_ty.clone())); - used[cand_idx] = true; + if hook_ty != target_ty { + warn!( + "skip hook {:?} due to param type mismatch at {}: hook {:?} vs target {:?}", + hook_ident, i, hook_ty, target_ty + ); + return; } + let arg = target_call + .arguments + .get(i) + .cloned() + .unwrap_or_else(|| SequenceArgument::Input(0)); + fixed_args.insert((param_offset + i) as u16, (arg, hook_ty)); } } @@ -262,67 +279,68 @@ where ) .cloned(); if let Some(target_abi) = target_abi - && let Some(hooks) = function_hooks.get(&func_ident) { - let mut hook_ctx: Option = None; - if let Some((create_ctx, _)) = context_idents.as_ref() { - if let Some((_, rets)) = append_function( - state, - &mut ptb, - create_ctx, - BTreeMap::new(), - BTreeMap::new(), - &vec![], - true, - 0, - ) { - hook_ctx = rets.first().cloned(); - } else { - warn!( - "Failed to append context creation function {:?}", - create_ctx - ); - } - } - for hook in hooks.pre_hooks.iter() { - append_hook_call( - state, - &mut ptb, - hook, - Some((&remapped_call, &target_abi, None)), - hook_ctx, - ); - } - let main_idx = ptb.commands.len() as u16; - ptb.commands - .push(MoveSequenceCall::Call(remapped_call.clone())); - index_map.push(main_idx); - for hook in hooks.post_hooks.iter() { - append_hook_call( - state, - &mut ptb, - hook, - Some((&remapped_call, &target_abi, Some(main_idx))), - hook_ctx, - ); - } - if let (Some(ctx_arg), Some((_, destroy_ctx)), Some(param_ty)) = - (hook_ctx, context_idents.as_ref(), destroy_param_ty.clone()) - { - let mut fixed_args = BTreeMap::new(); - fixed_args.insert(0u16, (ctx_arg, param_ty)); - let _ = append_function( - state, - &mut ptb, - destroy_ctx, - fixed_args, - BTreeMap::new(), - &vec![], - true, - 0, + && let Some(hooks) = function_hooks.get(&func_ident) + { + let mut hook_ctx: Option = None; + if let Some((create_ctx, _)) = context_idents.as_ref() { + if let Some((_, rets)) = append_function( + state, + &mut ptb, + create_ctx, + BTreeMap::new(), + BTreeMap::new(), + &vec![], + true, + 0, + ) { + hook_ctx = rets.first().cloned(); + } else { + warn!( + "Failed to append context creation function {:?}", + create_ctx ); } - continue; } + for hook in hooks.pre_hooks.iter() { + append_hook_call( + state, + &mut ptb, + hook, + Some((&remapped_call, &target_abi, None)), + hook_ctx, + ); + } + let main_idx = ptb.commands.len() as u16; + ptb.commands + .push(MoveSequenceCall::Call(remapped_call.clone())); + index_map.push(main_idx); + for hook in hooks.post_hooks.iter() { + append_hook_call( + state, + &mut ptb, + hook, + Some((&remapped_call, &target_abi, Some(main_idx))), + hook_ctx, + ); + } + if let (Some(ctx_arg), Some((_, destroy_ctx)), Some(param_ty)) = + (hook_ctx, context_idents.as_ref(), destroy_param_ty.clone()) + { + let mut fixed_args = BTreeMap::new(); + fixed_args.insert(0u16, (ctx_arg, param_ty)); + let _ = append_function( + state, + &mut ptb, + destroy_ctx, + fixed_args, + BTreeMap::new(), + &vec![], + true, + 0, + ); + } + continue; + } let new_idx = ptb.commands.len() as u16; ptb.commands.push(MoveSequenceCall::Call(remapped_call)); index_map.push(new_idx); diff --git a/crates/movy-fuzz/src/utils.rs b/crates/movy-fuzz/src/utils.rs index 83c7c4d..2ff4a9c 100644 --- a/crates/movy-fuzz/src/utils.rs +++ b/crates/movy-fuzz/src/utils.rs @@ -382,6 +382,7 @@ where let input = _testcase.input_mut().as_mut().unwrap(); let execution_outcome = outcome.exec; *input.outcome_mut() = Some(execution_outcome); + *input.display_mut() = Some(input.format()); } #[cfg(feature = "input_display")] diff --git a/crates/movy-static-analysis/src/sui/type_conversion.rs b/crates/movy-static-analysis/src/sui/type_conversion.rs index 0a95337..45779c4 100644 --- a/crates/movy-static-analysis/src/sui/type_conversion.rs +++ b/crates/movy-static-analysis/src/sui/type_conversion.rs @@ -34,32 +34,32 @@ fn detect_unnecessary_type_conversion(function: &FunctionInfo) -> bool { for instr in function.code.iter() { match instr { SLBytecode::Call(_, _, Operation::CastU8, srcs, _) => { - if function.local_types[srcs[0] ] == Type::Primitive(PrimitiveType::U8) { + if function.local_types[srcs[0]] == Type::Primitive(PrimitiveType::U8) { return true; } } SLBytecode::Call(_, _, Operation::CastU16, srcs, _) => { - if function.local_types[srcs[0] ] == Type::Primitive(PrimitiveType::U16) { + if function.local_types[srcs[0]] == Type::Primitive(PrimitiveType::U16) { return true; } } SLBytecode::Call(_, _, Operation::CastU32, srcs, _) => { - if function.local_types[srcs[0] ] == Type::Primitive(PrimitiveType::U32) { + if function.local_types[srcs[0]] == Type::Primitive(PrimitiveType::U32) { return true; } } SLBytecode::Call(_, _, Operation::CastU64, srcs, _) => { - if function.local_types[srcs[0] ] == Type::Primitive(PrimitiveType::U64) { + if function.local_types[srcs[0]] == Type::Primitive(PrimitiveType::U64) { return true; } } SLBytecode::Call(_, _, Operation::CastU128, srcs, _) => { - if function.local_types[srcs[0] ] == Type::Primitive(PrimitiveType::U128) { + if function.local_types[srcs[0]] == Type::Primitive(PrimitiveType::U128) { return true; } } SLBytecode::Call(_, _, Operation::CastU256, srcs, _) => { - if function.local_types[srcs[0] ] == Type::Primitive(PrimitiveType::U256) { + if function.local_types[srcs[0]] == Type::Primitive(PrimitiveType::U256) { return true; } } diff --git a/crates/movy-static-analysis/src/sui/unchecked_return.rs b/crates/movy-static-analysis/src/sui/unchecked_return.rs index 6fe5315..a836b43 100644 --- a/crates/movy-static-analysis/src/sui/unchecked_return.rs +++ b/crates/movy-static-analysis/src/sui/unchecked_return.rs @@ -42,10 +42,12 @@ fn detect_unchecked_return(function: &FunctionInfo) -> Vec { let mut idx = offset + 1; while idx < function.code.len() && consumed < dsts.len() { if let Bytecode::Call(_, _, Operation::Destroy, srcs, _) = &function.code[idx] - && srcs.len() == 1 && dsts.contains(&srcs[0]) { - consumed += 1; - break; - } + && srcs.len() == 1 + && dsts.contains(&srcs[0]) + { + consumed += 1; + break; + } idx += 1; } if consumed > 0 { diff --git a/test-data/counter/tests/movy.move b/test-data/counter/tests/movy.move index 64660cb..656765d 100644 --- a/test-data/counter/tests/movy.move +++ b/test-data/counter/tests/movy.move @@ -57,7 +57,7 @@ public fun movy_post_ptb( let (ctr_id, new_val) = extract_counter(ctr); let state = context::borrow_state(movy); let previous_val = bag::borrow(state, ctr_id); - if (*previous_val <= new_val) { + if (*previous_val > new_val) { crash_because(b"Counter should be always increasing".to_string()); } }