Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions decompiler/crates/kuna-decomp/src/p9_emit/kuna_pointerstorecast.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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!(
Expand All @@ -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(
Expand All @@ -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)
}
Expand Down
3 changes: 3 additions & 0 deletions decompiler/crates/kuna-decomp/src/p9_emit/printc/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
11 changes: 11 additions & 0 deletions docs/spec/09-emission.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`).
Loading