Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ build-tests/
.rusty-parity-*/
.rusty-parity-matrix/
.rusty-parity-matrix*/
.rusty-btreemap-check/
gcm.cache/
tests/transpile_tests/pollster/
tests/transpile_tests/serde_bytes/
Expand Down
153 changes: 150 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,19 @@ fn analyze_file(
}

// Parse the C++ file with include paths and defines
let ast = parser::parse_cpp_file_with_includes_defines_and_args(
let mut ast = parser::parse_cpp_file_with_includes_defines_and_args(
path,
&all_include_paths,
defines,
&extra_clang_args,
)?;
let main_file_canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.clone());
let is_transpiler_generated = is_transpiler_generated_cpp(path);
if is_transpiler_generated {
ast.functions.retain(|function| {
!is_generated_rusty_runtime_function(function, &main_file_canonical, true)
});
}

// Parse safety annotations using the unified rule
let mut safety_context = parser::safety_annotations::parse_safety_annotations(path)?;
Expand Down Expand Up @@ -256,8 +263,6 @@ fn analyze_file(
// from every consumer. Without this filter, large module-import graphs
// produce massive duplicate findings (e.g. each consumer of rrr.reactor
// re-flags every @safe→@unsafe call in Reactor's methods).
let main_file_canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.clone());

for function in &ast.functions {
// Skip system header functions - they shouldn't be analyzed internally
if is_system_header_or_std(&function.location.file, &function.name) {
Expand Down Expand Up @@ -416,6 +421,51 @@ fn analyze_file(
Ok(violations)
}

fn is_transpiler_generated_cpp(path: &Path) -> bool {
let Ok(contents) = std::fs::read_to_string(path) else {
return false;
};

contents
.lines()
.take(5)
.any(|line| line.trim() == "// Auto-generated by rusty-cpp-transpiler")
}

fn is_generated_rusty_runtime_function(
function: &parser::Function,
main_file_canonical: &Path,
is_transpiler_generated: bool,
) -> bool {
if !is_transpiler_generated || !function.name.starts_with("rusty::") {
return false;
}

// Drop rusty:: runtime functions whether they live in the .cppm body
// (emitted inline by the transpiler) or in include/rusty/* headers pulled
// in via #include. Both are library code, not the user fixture being
// checked, so neither belongs in the per-translation-unit violation list
// when the main file is transpiler-generated.
let fn_file = std::fs::canonicalize(&function.location.file)
.unwrap_or_else(|_| PathBuf::from(&function.location.file));
if fn_file == main_file_canonical {
return true;
}
is_rusty_runtime_include_path(&fn_file)
}

fn is_rusty_runtime_include_path(path: &Path) -> bool {
let mut prev_was_include = false;
for component in path.components() {
let name = component.as_os_str();
if prev_was_include && name == "rusty" {
return true;
}
prev_was_include = name == "include";
}
false
}

fn extract_compile_config_from_compile_commands(
cc_path: &PathBuf,
source_file: &PathBuf,
Expand Down Expand Up @@ -1086,4 +1136,101 @@ mod tests {
build_dir.join("CMakeFiles/rrr.pcm").display()
)));
}

fn test_function(name: &str, file: &Path) -> parser::Function {
parser::Function {
name: name.to_string(),
parameters: Vec::new(),
return_type: "void".to_string(),
body: Vec::new(),
location: parser::SourceLocation {
file: file.display().to_string(),
line: 1,
column: 1,
},
is_method: false,
method_qualifier: None,
template_parameters: Vec::new(),
safety_annotation: None,
has_explicit_safety_annotation: false,
is_deleted: false,
member_initializers: Vec::new(),
}
}

#[test]
fn detects_transpiler_generated_cpp_marker() {
let temp_dir = tempfile::tempdir().expect("create temp dir");
let generated = temp_dir.path().join("fixture.cppm");
fs::write(
&generated,
"// Auto-generated by rusty-cpp-transpiler\n// Do not edit manually.\n",
)
.expect("write generated marker");

let ordinary = temp_dir.path().join("ordinary.cpp");
fs::write(&ordinary, "int main() { return 0; }\n").expect("write ordinary file");

assert!(is_transpiler_generated_cpp(&generated));
assert!(!is_transpiler_generated_cpp(&ordinary));
}

#[test]
fn filters_generated_rusty_runtime_functions_in_module_and_headers() {
let temp_dir = tempfile::tempdir().expect("create temp dir");
let main_file = temp_dir.path().join("fixture.cppm");
let header_file = temp_dir.path().join("include/rusty/rusty.hpp");
let unrelated_header = temp_dir.path().join("include/third_party/lib.hpp");
let non_runtime_rusty_header = temp_dir.path().join("vendor/rusty/lib.hpp");
fs::create_dir_all(header_file.parent().expect("header parent"))
.expect("create rusty include dir");
fs::create_dir_all(unrelated_header.parent().expect("unrelated parent"))
.expect("create third_party include dir");
fs::create_dir_all(non_runtime_rusty_header.parent().expect("vendor parent"))
.expect("create vendor dir");
fs::write(&main_file, "").expect("write main file");
fs::write(&header_file, "").expect("write header file");
fs::write(&unrelated_header, "").expect("write unrelated header");
fs::write(&non_runtime_rusty_header, "").expect("write vendor header");
let main_canonical = main_file.canonicalize().expect("canonicalize main");

// rusty::* runtime helper emitted into the .cppm body is filtered.
assert!(is_generated_rusty_runtime_function(
&test_function("rusty::is_nan", &main_file),
&main_canonical,
true
));
// User fixture function is NOT filtered.
assert!(!is_generated_rusty_runtime_function(
&test_function("insert_then_get_present", &main_file),
&main_canonical,
true
));
// rusty::* function pulled in from an include/rusty/* header is
// also library noise on transpiler-generated input and is filtered.
assert!(is_generated_rusty_runtime_function(
&test_function("rusty::BTreeMap::get", &header_file),
&main_canonical,
true
));
// A rusty::* function defined OUTSIDE include/rusty/* (e.g. a
// user specialization in a third-party header) is NOT filtered.
assert!(!is_generated_rusty_runtime_function(
&test_function("rusty::special", &unrelated_header),
&main_canonical,
true
));
assert!(!is_generated_rusty_runtime_function(
&test_function("rusty::special", &non_runtime_rusty_header),
&main_canonical,
true
));
// Filter is opt-in via is_transpiler_generated; ordinary files
// are never filtered.
assert!(!is_generated_rusty_runtime_function(
&test_function("rusty::is_nan", &main_file),
&main_canonical,
false
));
}
}
Loading
Loading