use crate::decompiler::analysis::{MethodRef, MethodTable};
use crate::decompiler::cfg::{BasicBlock, BlockId, Cfg, EdgeKind, Terminator};
use crate::instruction::{Instruction, OpCode};
use crate::manifest::ContractManifest;
use super::extract::extract_method_cfgs;
use super::render::render_method_body;
use super::MethodView;
fn ins(offset: usize, op: OpCode) -> Instruction {
Instruction::new(offset, op, None)
}
fn two_method_whole_cfg() -> (Cfg, Vec<Instruction>) {
let mut cfg = Cfg::new();
cfg.add_block(BasicBlock::new(
BlockId(0),
0,
2,
0..2,
Terminator::Jump {
target: BlockId(10),
},
));
cfg.add_block(BasicBlock::new(
BlockId(2),
2,
2,
2..2,
Terminator::Jump {
target: BlockId(10),
},
));
cfg.add_edge(BlockId(0), BlockId(2), EdgeKind::Unconditional);
cfg.add_edge(BlockId(2), BlockId(10), EdgeKind::Unconditional);
cfg.add_block(BasicBlock::new(
BlockId(10),
10,
12,
10..12,
Terminator::Return,
));
let instructions = vec![
ins(0, OpCode::Push1),
ins(1, OpCode::Ret),
ins(10, OpCode::Push0),
ins(11, OpCode::Ret),
];
(cfg, instructions)
}
#[test]
fn extract_produces_two_sub_cfgs_with_cross_range_jump_rewritten() {
let (cfg, instructions) = two_method_whole_cfg();
let manifest_json = r#"{"name":"C","abi":{"methods":[
{"name":"main","parameters":[],"returntype":"Integer","offset":0},
{"name":"helper","parameters":[],"returntype":"Integer","offset":10}
]}}"#;
let manifest: ContractManifest = serde_json::from_str(manifest_json).unwrap();
let table = MethodTable::new(&instructions, Some(&manifest));
let views = extract_method_cfgs(&cfg, &table, &instructions);
assert_eq!(views.len(), 2, "expected two method spans");
let a = &views[0];
let a0 = a.cfg.block(BlockId(0)).expect("block 0 in A");
assert!(matches!(a0.terminator, Terminator::Return));
let b = &views[1];
let b10 = b.cfg.block(BlockId(10)).expect("block 10 in B");
assert!(matches!(b10.terminator, Terminator::Return));
assert!(a.cfg.block(BlockId(0)).is_some());
}
#[test]
fn render_method_body_emits_fn_with_return_type() {
let instructions = vec![
Instruction::new(0, OpCode::Push1, None),
Instruction::new(1, OpCode::Ret, None),
];
let mut cfg = Cfg::new();
cfg.add_block(BasicBlock::new(BlockId(0), 0, 2, 0..2, Terminator::Return));
let view = MethodView {
method: MethodRef {
offset: 0,
name: "main".to_string(),
},
cfg,
instructions,
};
let manifest_json = r#"{"name":"C","abi":{"methods":[
{"name":"main","parameters":[],"returntype":"Integer"}
]}}"#;
let manifest: ContractManifest = serde_json::from_str(manifest_json).unwrap();
let out = render_method_body(&view, Some(&manifest));
assert!(out.contains("fn main() -> int"), "got:\n{out}");
assert!(out.contains("return"), "got:\n{out}");
}