@@ -246,14 +246,24 @@ impl ProverBackend for CBMCBackend {
246246 }
247247
248248 async fn parse_file ( & self , path : PathBuf ) -> Result < ProofState > {
249- let content = tokio:: fs:: read_to_string ( path)
249+ let content = tokio:: fs:: read_to_string ( & path)
250250 . await
251251 . context ( "Failed to read C source file" ) ?;
252- self . parse_string ( & content) . await
252+ let mut state = self . parse_string ( & content) . await ?;
253+ state. metadata . insert (
254+ "source_path" . to_string ( ) ,
255+ serde_json:: Value :: String ( path. to_string_lossy ( ) . into_owned ( ) ) ,
256+ ) ;
257+ Ok ( state)
253258 }
254259
255260 async fn parse_string ( & self , content : & str ) -> Result < ProofState > {
256- self . parse_c_source ( content)
261+ let mut state = self . parse_c_source ( content) ?;
262+ state. metadata . insert (
263+ "cbmc_source" . to_string ( ) ,
264+ serde_json:: Value :: String ( content. to_string ( ) ) ,
265+ ) ;
266+ Ok ( state)
257267 }
258268
259269 async fn apply_tactic ( & self , state : & ProofState , tactic : & Tactic ) -> Result < TacticResult > {
@@ -321,7 +331,47 @@ impl ProverBackend for CBMCBackend {
321331 }
322332
323333 async fn verify_proof ( & self , state : & ProofState ) -> Result < bool > {
324- let c_source = self . to_c_source ( state) ?;
334+ // Determine unwind bound from metadata or use default
335+ let unwind = state
336+ . metadata
337+ . get ( "cbmc_unwind_bound" )
338+ . and_then ( |v| v. as_str ( ) )
339+ . and_then ( |s| s. parse :: < u32 > ( ) . ok ( ) )
340+ . unwrap_or ( self . unwind_bound ) ;
341+
342+ // Prefer the original .c source — `to_c_source(state)` round-trips
343+ // through the generic Term IR and silently mangles anything real.
344+ if let Some ( path) = state. metadata . get ( "source_path" ) . and_then ( |v| v. as_str ( ) ) {
345+ let output = tokio:: time:: timeout (
346+ tokio:: time:: Duration :: from_secs ( self . config . timeout + 10 ) ,
347+ Command :: new ( & self . config . executable )
348+ . arg ( "--unwind" )
349+ . arg ( format ! ( "{}" , unwind) )
350+ . arg ( path)
351+ . stdout ( Stdio :: piped ( ) )
352+ . stderr ( Stdio :: piped ( ) )
353+ . output ( ) ,
354+ )
355+ . await
356+ . map_err ( |_| {
357+ anyhow ! (
358+ "CBMC verification timed out after {} seconds" ,
359+ self . config. timeout
360+ )
361+ } ) ?
362+ . context ( "Failed to execute CBMC" ) ?;
363+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
364+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
365+ let combined = format ! ( "{}\n {}" , stdout, stderr) ;
366+ return self . parse_result ( & combined) ;
367+ }
368+
369+ let c_source = if let Some ( src) = state. metadata . get ( "cbmc_source" ) . and_then ( |v| v. as_str ( ) )
370+ {
371+ src. to_string ( )
372+ } else {
373+ self . to_c_source ( state) ?
374+ } ;
325375
326376 // Write C source to a temporary file (CBMC requires a file)
327377 let tmp_dir =
@@ -331,14 +381,6 @@ impl ProverBackend for CBMCBackend {
331381 . await
332382 . context ( "Failed to write temporary C file" ) ?;
333383
334- // Determine unwind bound from metadata or use default
335- let unwind = state
336- . metadata
337- . get ( "cbmc_unwind_bound" )
338- . and_then ( |v| v. as_str ( ) )
339- . and_then ( |s| s. parse :: < u32 > ( ) . ok ( ) )
340- . unwrap_or ( self . unwind_bound ) ;
341-
342384 // Run cbmc with unwind bound
343385 let output = tokio:: time:: timeout (
344386 tokio:: time:: Duration :: from_secs ( self . config . timeout + 10 ) ,
0 commit comments