diff --git a/examples/assert-debug-test/Cargo.lock b/examples/assert-debug-test/Cargo.lock new file mode 100644 index 000000000..f0e1b3e85 --- /dev/null +++ b/examples/assert-debug-test/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "assert_debug_test" +version = "0.1.0" diff --git a/examples/assert-debug-test/Cargo.toml b/examples/assert-debug-test/Cargo.toml new file mode 100644 index 000000000..36cc5166a --- /dev/null +++ b/examples/assert-debug-test/Cargo.toml @@ -0,0 +1,19 @@ +cargo-features = ["trim-paths"] + +[package] +name = "assert_debug_test" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +debug = true +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +debug = true +panic = "abort" +trim-paths = ["diagnostics", "object"] diff --git a/examples/assert-debug-test/src/lib.rs b/examples/assert-debug-test/src/lib.rs new file mode 100644 index 000000000..5ff11bad5 --- /dev/null +++ b/examples/assert-debug-test/src/lib.rs @@ -0,0 +1,37 @@ +//! Example for testing Rust assert! macro source location preservation. +//! +//! Build with: +//! cargo build --release --target wasm32-unknown-unknown \ +//! --manifest-path examples/assert-debug-test/Cargo.toml +//! +//! Check HIR for source locations: +//! ./bin/midenc examples/assert-debug-test/target/wasm32-unknown-unknown/release/assert_debug_test.wasm \ +//! --entrypoint=assert_debug_test::test_assert \ +//! -Ztrim-path-prefix=examples/assert-debug-test \ +//! -Zprint-hir-source-locations \ +//! --debug full --emit=hir=- +//! + +#![no_std] + +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + core::arch::wasm32::unreachable() +} + +#[no_mangle] +pub extern "C" fn test_assert(x: u32) -> u32 { + assert!(x > 100); + x +} + +#[no_mangle] +pub extern "C" fn test_multiple_asserts(a: u32, b: u32) -> u32 { + assert!(a > 0); + + assert!(b > 0); + + assert!(a != b); + + a + b +} diff --git a/tests/integration/src/rust_masm_tests/debug_source_locations.rs b/tests/integration/src/rust_masm_tests/debug_source_locations.rs new file mode 100644 index 000000000..9f7c2fe47 --- /dev/null +++ b/tests/integration/src/rust_masm_tests/debug_source_locations.rs @@ -0,0 +1,165 @@ +//! Tests that verify debug source location information is correctly preserved +//! from Rust source code through to MASM compilation and execution. +//! + +use std::panic::{self, AssertUnwindSafe}; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; + +use miden_core::Felt; +use miden_debug::Executor; +use miden_lib::MidenLib; +use midenc_compile::compile_to_memory; +use midenc_session::{InputFile, STDLIB}; + +use crate::testing::setup; + +// Get path to examples/assert-debug-test test. +fn get_assert_debug_test_path() -> PathBuf { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") + .unwrap_or_else(|_| std::env::current_dir().unwrap().to_str().unwrap().to_string()); + PathBuf::from(manifest_dir) + .parent() + .unwrap() + .parent() + .unwrap() + .join("examples") + .join("assert-debug-test") +} + +fn create_executor_with_std( + args: Vec, + package: &miden_mast_package::Package, +) -> Executor { + let mut exec = Executor::new(args); + let std_library = (*STDLIB).clone(); + exec.dependency_resolver_mut() + .add(*std_library.digest(), std_library.clone().into()); + let base_library = Arc::new(MidenLib::default().as_ref().clone()); + exec.dependency_resolver_mut() + .add(*base_library.digest(), base_library.clone().into()); + exec.with_dependencies(package.manifest.dependencies()) + .expect("Failed to set up dependencies"); + exec +} + +#[test] +fn test_rust_assert_macro_source_location_with_debug_executor() { + setup::enable_compiler_instrumentation(); + + let example_path = get_assert_debug_test_path(); + let example_path_str = example_path.to_string_lossy(); + + let manifest_path = example_path.join("Cargo.toml"); + let status = Command::new("cargo") + .args([ + "build", + "--release", + "--target", + "wasm32-unknown-unknown", + "--manifest-path", + manifest_path.to_str().unwrap(), + ]) + .status() + .expect("Failed to run cargo build"); + assert!(status.success(), "Failed to build assert-debug-test example"); + + let wasm_path = example_path + .join("target") + .join("wasm32-unknown-unknown") + .join("release") + .join("assert_debug_test.wasm"); + + let input_file = InputFile::from_path(&wasm_path).expect("Failed to load wasm file"); + let context = setup::default_context( + [input_file], + &[ + "--debug", + "full", + &format!("-Ztrim-path-prefix={}", example_path_str), + "--entrypoint", + "assert_debug_test::test_assert", + ], + ); + + let artifact = compile_to_memory(context.clone()) + .expect("Failed to compile wasm to masm"); + let package = artifact.unwrap_mast(); + let program = package.unwrap_program(); + let session = context.session_rc(); + + // First, test that the function works when assertion passes (x > 100) + { + let args = vec![Felt::new(200)]; + let exec = create_executor_with_std(args, &package); + + let trace = exec.execute(&program, session.source_manager.clone()); + let result: u32 = trace.parse_result().expect("Failed to parse result"); + assert_eq!(result, 200, "When x > 100, function should return x"); + eprintln!("SUCCESS: Assertion passed when x=200 > 100"); + } + + // Now test that when assertion fails (x <= 100), we get a panic with source location + { + let args = vec![Felt::new(50)]; // x = 50, assert!(50 > 100) fails + let exec = create_executor_with_std(args, &package); + + // Clone values needed for the closure + let program_clone = program.clone(); + let source_manager = session.source_manager.clone(); + + // Capture the panic output + let result = panic::catch_unwind(AssertUnwindSafe(move || { + exec.execute(&program_clone, source_manager) + })); + + // The execution should panic (fail) because assert!(50 > 100) fails + assert!( + result.is_err(), + "Execution should have panicked due to failed assertion (x=50 <= 100)" + ); + + // Check the panic message for source location information + if let Err(panic_info) = result { + let panic_message = if let Some(s) = panic_info.downcast_ref::() { + s.clone() + } else if let Some(s) = panic_info.downcast_ref::<&str>() { + s.to_string() + } else { + "Unknown panic".to_string() + }; + + eprintln!("\n=== Panic message from failed assertion ==="); + eprintln!("{panic_message}"); + eprintln!("============================================\n"); + + // The panic message should indicate an assertion failure + assert!( + panic_message.contains("assertion failed"), + "Panic message should indicate assertion failure. Got: {panic_message}" + ); + + // Check if source location info is present + let has_source_file = panic_message.contains("lib.rs") + || panic_message.contains("src/"); + + let has_line_info = panic_message.contains(":32") + || panic_message.contains(":33"); + + let has_any_source_info = has_source_file || has_line_info; + + // FIXME: Currently source locations show in stack traces. + // This test documents the current behavior. + eprintln!( + "SUCCESS: Assertion correctly failed when x=50 <= 100" + ); + eprintln!("Has source file reference: {}", has_source_file); + eprintln!("Has line info: {}", has_line_info); + + if has_any_source_info { + eprintln!("Source locations are being resolved!"); + } + } + } +} diff --git a/tests/integration/src/rust_masm_tests/mod.rs b/tests/integration/src/rust_masm_tests/mod.rs index cdcc68eca..649e4b58c 100644 --- a/tests/integration/src/rust_masm_tests/mod.rs +++ b/tests/integration/src/rust_masm_tests/mod.rs @@ -12,6 +12,7 @@ use crate::testing::eval_package; mod abi_transform; mod apps; +mod debug_source_locations; mod examples; mod instructions; mod intrinsics;