diff --git a/decompiler/crates/kuna-decomp/src/p9_emit/kuna_pointerstorecast.rs b/decompiler/crates/kuna-decomp/src/p9_emit/kuna_pointerstorecast.rs index eff330c84b..acea853eff 100644 --- a/decompiler/crates/kuna-decomp/src/p9_emit/kuna_pointerstorecast.rs +++ b/decompiler/crates/kuna-decomp/src/p9_emit/kuna_pointerstorecast.rs @@ -1,4 +1,4 @@ -//! Integer members require explicit conversions when an asserted pointer names the value. +//! Pointer and integer storage members require explicit C representation conversions. use crate::context::OpId; use crate::dtype::{type_metatype, Datatype}; @@ -15,17 +15,29 @@ pub(super) fn target( let operation = fd.obank().get(op)?; if !matches!( operation.code(), - OpCode::CPUI_COPY | OpCode::CPUI_PTRADD | OpCode::CPUI_PTRSUB + OpCode::CPUI_COPY | OpCode::CPUI_CAST | OpCode::CPUI_PTRADD | OpCode::CPUI_PTRSUB ) { return None; } let input = operation.get_in(0)?; let output = operation.get_out()?; - let source = fd.vn_type_read_facing(input, op); - if source.get_metatype() != type_metatype::TYPE_PTR { + // A CAST expression prints its result type, rather than its input type. + let source = if operation.code() == OpCode::CPUI_CAST { + fd.vn_type_def_facing(output) + } else { + fd.vn_type_read_facing(input, op) + }; + if !matches!( + source.get_metatype(), + type_metatype::TYPE_PTR + | type_metatype::TYPE_INT + | type_metatype::TYPE_UINT + | type_metatype::TYPE_UNKNOWN + ) { return None; } if operation.code() == OpCode::CPUI_COPY + && source.get_metatype() == type_metatype::TYPE_PTR && crate::printc::declared_variable_type(fd, decl_high_type, input) .is_some_and(|declared| { matches!( @@ -38,10 +50,20 @@ pub(super) fn target( } let value = fd.vbank().get(output)?; let target = printed().or_else(|| { - let declared = super::kuna_bitcast::declared_member_type(fd, decl_high_type, output)?; - let offset = value - .get_high() - .and_then(|id| fd.high_bank().get(id)) + let high = value.get_high().and_then(|id| fd.high_bank().get(id)); + let declared = high + .and_then(|high| high.kuna_symbol_type()) + .filter(|ty| { + matches!( + ty.get_metatype(), + type_metatype::TYPE_STRUCT + | type_metatype::TYPE_ARRAY + | type_metatype::TYPE_UNION + ) + }) + .cloned() + .or_else(|| super::kuna_bitcast::declared_member_type(fd, decl_high_type, output))?; + let offset = high .map(|high| high.kuna_symbol_offset().max(0)) .unwrap_or(0); crate::kuna_stackviews::scalar_piece( @@ -51,10 +73,18 @@ pub(super) fn target( value.get_size(), ) })?; - (matches!( - target.get_metatype(), - type_metatype::TYPE_INT | type_metatype::TYPE_UINT - ) && target.get_size() == source.get_size() + let pointer_to_word = source.get_metatype() == type_metatype::TYPE_PTR + && matches!( + target.get_metatype(), + type_metatype::TYPE_INT | type_metatype::TYPE_UINT + ); + let word_to_pointer = target.get_metatype() == type_metatype::TYPE_PTR + && matches!( + source.get_metatype(), + type_metatype::TYPE_INT | type_metatype::TYPE_UINT | type_metatype::TYPE_UNKNOWN + ); + ((pointer_to_word || word_to_pointer) + && target.get_size() == source.get_size() && target.get_size() == value.get_size()) .then_some(target) } diff --git a/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests.rs b/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests.rs index df62ac5247..b386271979 100644 --- a/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests.rs +++ b/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests.rs @@ -2263,6 +2263,9 @@ mod w10_printc_cast_render { op } + #[path = "pointer_assignments.rs"] + mod pointer_assignments; + /// Build a 1-input cast op (`opc`) whose in0 is a constant `k` (renders as a /// literal, deterministic) and whose OUTPUT carries the data-type `out_ty`. fn build_cast_over_const( diff --git a/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests/w10_printc_cast_render/pointer_assignments.rs b/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests/w10_printc_cast_render/pointer_assignments.rs new file mode 100644 index 0000000000..778e4b5b53 --- /dev/null +++ b/decompiler/crates/kuna-decomp/src/p9_emit/printc/tests/w10_printc_cast_render/pointer_assignments.rs @@ -0,0 +1,127 @@ +//! Pointer declarations can differ from register integer carriers. +use super::*; + +#[test] +fn integer_carriers_and_cast_results_assign_to_pointer_members() { + use crate::dtype::{DatatypeKind, TypeField}; + use std::process::Command; + for size in [4, 8] { + for opcode in [OpCode::CPUI_COPY, OpCode::CPUI_CAST] { + let types = Rc::new(crate::dtype::TypeFactoryImpl::new()); + let mut context = ContextArch::new(build_manager()); + context.types = Some(types); + let space = Rc::clone(context.manage().get_space_by_name("ram").unwrap()); + let mut fd = Funcdata::new( + "emit", + "emit", + Rc::new(context), + Address::new(Rc::clone(&space), 0x1000), + 0x1000, + 0x40, + ) + .unwrap(); + let word_name = if size == 4 { + "unsigned int" + } else { + "unsigned long long" + }; + let word = named(word_name, size, type_metatype::TYPE_UINT); + let mut pointer = Datatype::new_with_align(size, size, type_metatype::TYPE_PTR); + pointer.kind = DatatypeKind::Pointer { + ptrto: named("unsigned char", 1, type_metatype::TYPE_UINT), + spaceid: None, + truncate: None, + wordsize: 1, + }; + let pointer = Rc::new(pointer); + let input_type = if opcode == OpCode::CPUI_CAST { + Rc::clone(&pointer) + } else { + Rc::clone(&word) + }; + let input = fd.new_varnode( + size, + &Address::new(Rc::clone(&space), 0x400), + Some(input_type), + ); + // The emitted field is a pointer; the IR carrier keeps its word type. + let output = fd.new_varnode(size, &Address::new(space, 0x500), Some(Rc::clone(&word))); + let op = mk_op(&mut fd, 1, 0x20, opcode); + fd.op_set_input(op, input, 0).unwrap(); + fd.op_set_output(op, output).unwrap(); + fd.set_high_level(); + let high = fd.vbank().get(input).unwrap().get_high().unwrap(); + fd.high_bank_mut() + .get_mut(high) + .unwrap() + .set_kuna_name("value"); + let mut frame = Datatype::new_with_align(size * 2, size, type_metatype::TYPE_STRUCT); + frame.name = "Frame".into(); + frame.display_name = frame.name.clone(); + frame.id = Datatype::hash_name(&frame.name); + frame.kind = DatatypeKind::Struct { + field: vec![ + TypeField::new(0, 0, "length", Rc::clone(&word)), + TypeField::new(1, size, "slot", pointer), + ], + bitfield: vec![], + }; + let high = fd.vbank().get(output).unwrap().get_high().unwrap(); + let high = fd.high_bank_mut().get_mut(high).unwrap(); + high.set_kuna_name("frame"); + high.set_symbol_type(Rc::new(frame)); + high.set_symbol_offset(size); + let mut printer = PrintC::new(); + printer.set_output_stream(); + printer.emit_statement(&fd, &bare_arch(), op); + let statement = printer.emit_mut().output_str(); + let argument = if opcode == OpCode::CPUI_CAST { + "unsigned char *" + } else { + word_name + }; + let call = if opcode == OpCode::CPUI_CAST { + "bytes+13".to_owned() + } else { + format!("({word_name})(__UINTPTR_TYPE__)(bytes+13)") + }; + let program = format!("typedef struct {{ {word_name} length; unsigned char *slot; }} Frame;\nstatic Frame emit({argument} value) {{ Frame frame; frame.length=0; {statement} return frame; }}\nint main(void) {{ unsigned char bytes[32]; return emit({call}).slot != bytes+13; }}\n"); + if Command::new("clang").arg("--version").output().is_err() { + continue; + } + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let source = std::env::temp_dir().join(format!( + "kuna-pointer-member-{}-{nonce}.c", + std::process::id() + )); + std::fs::write(&source, program).unwrap(); + for optimization in ["-O0", "-O2"] { + let output = source.with_extension(if size == 4 { "o" } else { "exe" }); + let mut compiler = Command::new("clang"); + compiler.args(["-std=gnu11", "-Wall", "-Wextra", "-Werror", optimization]); + if size == 4 { + compiler.args(["--target=i686-w64-windows-gnu", "-c"]); + } + let built = compiler + .arg(&source) + .arg("-o") + .arg(&output) + .output() + .unwrap(); + assert!( + built.status.success(), + "{opcode:?} size={size}: {statement}\n{}", + String::from_utf8_lossy(&built.stderr) + ); + if size == 8 { + assert!(Command::new(&output).status().unwrap().success()); + } + std::fs::remove_file(output).unwrap(); + } + std::fs::remove_file(source).unwrap(); + } + } +} diff --git a/docs/spec/09-emission.md b/docs/spec/09-emission.md index cd8f6cfa04..53e5573ae8 100644 --- a/docs/spec/09-emission.md +++ b/docs/spec/09-emission.md @@ -3931,3 +3931,14 @@ form so each arm stays a statement LOAD copying into the member's type A STORE helper likewise initializes its compound literal with the rendered source member’s same-width scalar type (`printc.rs (op_store_ir)`), preserving its bytes when propagated IR types disagree. + +A same-width integer representation assigned to a declared pointer member needs +an explicit pointer cast in C. CAST statements are checked using the type their +result expression prints, rather than the input type, so a pointer-to-integer +cast cannot leave an integer expression assigned to a pointer field, and a +pointer-typed CAST assigned to an integer variable receives the integer cast +(`len = (long)(char *)&len[1];`) instead of a bare pointer. When the +variable's Symbol is a structure, array or union, that whole Symbol supplies the +member type, since the declaration representative covers only the field. A +scalar local or global keeps the type its declaration prints, which can differ +from its Symbol's scalar type (`kuna_pointerstorecast.rs (target)`).