Skip to content

Commit 515aa8b

Browse files
committed
feat: add functionality to handle the new argument name hints in the decompiler
1 parent ad27729 commit 515aa8b

6 files changed

Lines changed: 46 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ All notable changes to this project are documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [0.10.0] - 2026-07-27
8+
## [0.10.0] - TODO
99

1010
### Added
1111

12+
- Add functionality to handle the new argument name hints in the Hex-Rays decompiler.
1213
- Add an integration test to check naming and decompilation functionalities.
1314

1415
### Changed

CLAUDE.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ The build script (`build.rs`) checks common default locations as a fallback, but
2323
cargo build # debug (debug info stripped for faster startup)
2424
cargo build --release # optimized, LTO, stripped
2525

26-
# Test (unit tests only, no IDA Pro required)
27-
cargo test --lib
28-
2926
# Test (integration tests, custom harness, tests against ./tests/data/ls)
3027
cargo test
3128
cargo test --test tests -- --nocapture # verbose
@@ -72,6 +69,6 @@ The crate-level documentation in `src/lib.rs` is assembled in a specific order t
7269

7370
## Tests
7471

75-
**Unit tests** live in `src/lib.rs` under `#[cfg(test)] mod tests`. They do not require IDA Pro and run with `cargo test --lib`. Only executed in CI on Linux; macOS and Windows cannot run them because `dyld`/the Windows loader requires all linked dylibs (including `libida`) to be present at process startup, whereas Linux's lazy binding allows the test binary to start without resolving IDA symbols. They cover `prepare_output_dir` (create, empty-dir recreate, non-empty failure) and `sanitize_filename` (plain names, reserved-char replacement, truncation).
72+
**Unit tests** live in `src/lib.rs` under `#[cfg(test)] mod tests`. They cover `prepare_output_dir` (create, empty-dir recreate, non-empty failure) and `sanitize_filename` (plain names, reserved-char replacement, truncation).
7673

7774
**Integration tests** live in `tests/main.rs` with `harness = false` (custom runner). They require IDA Pro to be available and `IDADIR` set. The test binary is `tests/data/ls` (x86-64 ELF). Tests validate function count, output file count, output directory behavior (non-empty dir error, empty-dir success), the `decompile_to_file` API, pseudocode content, a spot-check of a known output file (`sub_4AD0@4AD0.c`) to verify the naming scheme, and error-path behavior (read-only files, path length limits, invalid filenames).

Cargo.lock

Lines changed: 3 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ thiserror = "2.0"
2929
[build-dependencies]
3030
idalib-build = "0.10"
3131

32+
[patch.crates-io]
33+
idalib = { git = "https://github.com/0xdea/idalib", branch = "feature/hexrays-improvements" }
34+
idalib-build = { git = "https://github.com/0xdea/idalib", branch = "feature/hexrays-improvements" }
35+
idalib-sys = { git = "https://github.com/0xdea/idalib", branch = "feature/hexrays-improvements" }
36+
3237
[[test]]
3338
name = "tests"
3439
path = "tests/main.rs"

src/lib.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ pub enum HaruspexError {
3636
FileWriteFailed(#[from] io::Error),
3737
}
3838

39+
/// Argument name hints mode for function calls in pseudocode.
40+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
41+
#[repr(u8)]
42+
#[non_exhaustive]
43+
pub enum ArgHintsMode {
44+
/// Argument name hints are disabled.
45+
Disabled = 0,
46+
/// Argument names are displayed as comments (/*param=*/).
47+
Comment,
48+
/// Argument names are displayed as inlay hints (param:).
49+
Inlay,
50+
}
51+
52+
impl ArgHintsMode {
53+
/// Returns the directive to use for setting the argument hints mode.
54+
#[expect(
55+
clippy::as_conversions,
56+
reason = "argument hints mode is stored as a `u8`"
57+
)]
58+
fn directive(self) -> String {
59+
format!("ARG_HINTS_MODE = {}", self as u8)
60+
}
61+
}
62+
3963
/// Extracts pseudocode of functions in the binary file at `filepath` and saves it in `filepath.dec`.
4064
///
4165
/// Returns how many functions were decompiled.
@@ -91,7 +115,7 @@ pub fn run(filepath: impl AsRef<Path>) -> anyhow::Result<usize> {
91115
clippy::arithmetic_side_effects,
92116
reason = "`usize` can hardly overflow here"
93117
)]
94-
match decompile_to_file(&idb, &f, &output_path) {
118+
match decompile_to_file(&idb, &f, &output_path, ArgHintsMode::Disabled) {
95119
// Print the output path in case of successful function decompilation.
96120
Ok(()) => {
97121
println!("{func_name} -> `{}`", output_path.display());
@@ -152,7 +176,7 @@ pub fn run(filepath: impl AsRef<Path>) -> anyhow::Result<usize> {
152176
/// .find(|(_, f)| f.name().unwrap() == "main")
153177
/// .unwrap();
154178
///
155-
/// haruspex::decompile_to_file(&idb, &func, &output_file)?;
179+
/// haruspex::decompile_to_file(&idb, &func, &output_file, haruspex::ArgHintsMode::Disabled)?;
156180
/// # std::fs::remove_file(output_file)?;
157181
/// # Ok::<(), anyhow::Error>(())
158182
/// ```
@@ -161,7 +185,11 @@ pub fn decompile_to_file(
161185
idb: &IDB,
162186
func: &Function,
163187
filepath: impl AsRef<Path>,
188+
hints_mode: ArgHintsMode,
164189
) -> Result<(), HaruspexError> {
190+
// Set argument name hints mode.
191+
idb.change_hexrays_config(hints_mode.directive())?;
192+
165193
// Decompile function.
166194
let decomp = idb.decompile(func)?;
167195
let source = decomp.pseudocode();

tests/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::fs;
44
use std::path::Path;
55

6-
use haruspex::HaruspexError;
6+
use haruspex::{ArgHintsMode, HaruspexError};
77
use idalib::idb::IDB;
88

99
/// Custom harness for integration tests.
@@ -82,7 +82,7 @@ fn main() -> anyhow::Result<()> {
8282
.find(|f| f.1.name().expect("invalid function name") == "main")
8383
.expect("failed to find function `main`");
8484
let output_file = dirpath.join("main.c");
85-
haruspex::decompile_to_file(&idb, &func, &output_file)?;
85+
haruspex::decompile_to_file(&idb, &func, &output_file, ArgHintsMode::Disabled)?;
8686
assert!(
8787
output_file.metadata()?.len() > 0,
8888
"output file `{}` is empty",
@@ -120,7 +120,7 @@ fn main() -> anyhow::Result<()> {
120120
let mut perms = output_file.metadata()?.permissions();
121121
perms.set_readonly(true);
122122
fs::set_permissions(&output_file, perms)?;
123-
let result = haruspex::decompile_to_file(&idb, &func, &output_file);
123+
let result = haruspex::decompile_to_file(&idb, &func, &output_file, ArgHintsMode::Disabled);
124124
assert!(result.is_err(), "file write succeeded unexpectedly");
125125
assert!(
126126
matches!(result, Err(HaruspexError::FileWriteFailed(_))),
@@ -136,7 +136,7 @@ fn main() -> anyhow::Result<()> {
136136
// Check `decompile_to_file` handles file length limitations.
137137
print!("[*] Checking `decompile_to_file` handles file length limitations... ");
138138
let output_file = dirpath.join("A".repeat(2048));
139-
let result = haruspex::decompile_to_file(&idb, &func, &output_file);
139+
let result = haruspex::decompile_to_file(&idb, &func, &output_file, ArgHintsMode::Disabled);
140140
assert!(result.is_err(), "file write succeeded unexpectedly");
141141
assert!(
142142
matches!(result, Err(HaruspexError::FileWriteFailed(_))),
@@ -150,7 +150,7 @@ fn main() -> anyhow::Result<()> {
150150
let output_file = dirpath.join("invalid/filename");
151151
#[cfg(windows)]
152152
let output_file = dirpath.join("invalid<>?*filename");
153-
let result = haruspex::decompile_to_file(&idb, &func, &output_file);
153+
let result = haruspex::decompile_to_file(&idb, &func, &output_file, ArgHintsMode::Disabled);
154154
assert!(result.is_err(), "file write succeeded unexpectedly");
155155
assert!(
156156
matches!(result, Err(HaruspexError::FileWriteFailed(_))),

0 commit comments

Comments
 (0)