Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4aa7d01
split_ffi_entry_points: initial commit; comment-preserving TokenStrea…
spernsteiner Oct 29, 2025
cc34964
split_ffi: working implementation
spernsteiner Oct 31, 2025
b3345e3
split_ffi: fix warnings
spernsteiner Oct 31, 2025
dfdf319
split_ffi: fix rust-analyzer version in cargo.toml
spernsteiner Oct 31, 2025
ab34c14
split_ffi: refuse to rewrite files outside cargo dir
spernsteiner Oct 31, 2025
155045d
split_ffi: refactor: move TokenStream rewriting into new rewrite_util…
spernsteiner Nov 5, 2025
45cd826
rename rewrite_util -> rust_util::rewrite
spernsteiner Nov 10, 2025
c994b35
rust_util: add FileCollector
spernsteiner Nov 10, 2025
f740a77
tools/split_rust: initial commit
spernsteiner Nov 10, 2025
f88e410
rust_util: track module path in FileCollector
spernsteiner Nov 12, 2025
bcc86c6
split_rust: output code snippets instead of spans
spernsteiner Nov 13, 2025
1091e04
refactor: move ItemSpanVisitor from split_rust into rust_util
spernsteiner Nov 13, 2025
8e8456f
add tools/merge_rust/
spernsteiner Nov 13, 2025
c4effc6
tools/merge_rust: use clap for arguments
fw-immunant Nov 24, 2025
7dc2fb8
tools/split_rust: use clap for arguments
fw-immunant Dec 1, 2025
40f4a4d
tools/split_ffi_entry_points: use clap for arguments
fw-immunant Dec 6, 2025
a0a4e76
tools/split_ffi_entry_points: use "visit" syn feature
fw-immunant Nov 24, 2025
a2a241f
tools/rust_util: use "visit" syn feature
fw-immunant Nov 24, 2025
265e101
tools/rust_util: do not include r# prefix in fs paths
fw-immunant Dec 7, 2025
60776ca
cargo: exclude tools/ from Cargo workspace
fw-immunant Dec 6, 2025
b14877a
tools: add README
fw-immunant Dec 6, 2025
c689e5e
tools: add rust-toolchain.toml, fixing README build command with rustup
fw-immunant Dec 12, 2025
44f79cc
tools: use match ergonomics
fw-immunant Dec 12, 2025
6739500
tools: move doc comment above attributes
fw-immunant Dec 12, 2025
84450a9
tools: rustfmt
fw-immunant Dec 12, 2025
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exclude = [
"analysis/tests",
"examples",
"tests",
"tools",
]

[workspace.package]
Expand Down
20 changes: 20 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# C2Rust CRISP tools

- `merge_rust`
- `split_rust`
- `split_ffi_entry_points`

## Building

These tools rely on rust-analyzer's libraries, so they need a relatively recent version of Rust. Run `cargo build --release` in their respective directories to build them.

## Running

`split_rust` and `merge_rust` expect the root source file of a Rust project as their first argument, e.g. `lib.rs` or `main.rs`.

- `merge_rust` modifies the specified codebase in-place.
- `split_rust` emits JSON on standard output.

`split_ffi_entry_points` expects a Rust project directory (the directory containing a `Cargo.toml` file) as its only argument.

It modifies that Rust project in-place.
260 changes: 260 additions & 0 deletions tools/merge_rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions tools/merge_rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "merge_rust"
version = "0.1.0"
edition = "2024"

[dependencies]
rust_util = { path = "../rust_util" }

syn = { version = "2.0.108", features = ["full", "visit"] }
proc-macro2 = { version = "1", features = ["span-locations"] }
clap = { version = "4.5", features = ["derive"] }
quote = "1"

serde_json = "1"
70 changes: 70 additions & 0 deletions tools/merge_rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use clap::Parser;
use rust_util::collect::FileCollector;
use rust_util::item_span::item_spans;
use serde_json;
use std::collections::HashMap;
use std::fs::{self, File};
use std::path::PathBuf;

/// Merge updated item definitions into a Rust codebase.
#[derive(Parser)]
struct Args {
/// Root Rust source file to update (`lib.rs` or `main.rs`).
src_root_path: PathBuf,
/// JSON file containing mapping from Rust item paths to desired new contents.
new_snippets_file: PathBuf,
}

fn main() {
let args = Args::parse();
let src_root_path = args.src_root_path;
let new_snippet_json_path = args.new_snippets_file;

let new_snippets_file = File::open(&new_snippet_json_path).unwrap();
let new_snippets: HashMap<String, String> = serde_json::from_reader(new_snippets_file).unwrap();

let mut fc = FileCollector::default();
fc.parse(&src_root_path, vec![], true).unwrap();
for &(ref file_path, ref mod_path, ref ast) in &fc.files {
eprintln!("visit {:?}", file_path);
let old_src = fs::read_to_string(file_path).unwrap();

let mut rewrites = Vec::new();
for (item_path, lo, hi) in item_spans(mod_path.to_owned(), ast) {
let old_snippet = &old_src[lo..hi];
let item_path_str = item_path.join("::");
if let Some(new_snippet) = new_snippets.get(&item_path_str) {
if new_snippet != old_snippet {
rewrites.push((lo, hi, new_snippet));
}
}
}

if rewrites.len() == 0 {
continue;
}

rewrites.sort_by_key(|&(lo, _, _)| lo);
let mut new_src = String::with_capacity(old_src.len());
let mut pos = 0;
for &(lo, hi, new_snippet) in &rewrites {
assert!(
lo >= pos,
"overlapping rewrites: previous rewrite ended at {}, \
but current rewrite covers {} .. {}",
pos,
lo,
hi
);
new_src.push_str(&old_src[pos..lo]);
new_src.push_str(new_snippet);
pos = hi;
}
new_src.push_str(&old_src[pos..]);

let tmp_path = file_path.with_extension(".new");
fs::write(&tmp_path, &new_src).unwrap();
fs::rename(&tmp_path, file_path).unwrap();
eprintln!("applied {} rewrites to {:?}", rewrites.len(), file_path);
}
}
Loading