@@ -2,8 +2,9 @@ use colored::Colorize;
22use indexmap:: IndexMap ;
33use memoize:: memoize;
44use std:: collections:: { HashMap , HashSet } ;
5+ use std:: ffi:: OsStr ;
56use std:: fs:: { self , File } ;
6- use std:: io:: { Read , Write } ;
7+ use std:: io:: { BufRead , BufReader , IsTerminal , Read , Write } ;
78use std:: path:: { Path , PathBuf } ;
89use std:: process:: { Command , Stdio } ;
910use std:: time:: Instant ;
@@ -648,7 +649,7 @@ pub fn exec_verify(targets: &[VerusTarget], options: &ExtraOptions) -> Result<()
648649 ) ;
649650 debug ! ( ">> {:?}" , cmd) ;
650651
651- let status = cmd . status ( ) . unwrap_or_else ( |e| {
652+ let status = run_verify_command ( cmd ) . unwrap_or_else ( |e| {
652653 error ! ( "Error during verification: {}" , e) ;
653654 } ) ;
654655
@@ -712,6 +713,104 @@ pub fn exec_verify(targets: &[VerusTarget], options: &ExtraOptions) -> Result<()
712713 Ok ( ( ) )
713714}
714715
716+ const VERUS_SPEC_WARNING_START : & str =
717+ "warning: #[verus_spec] is likely used inside a verus! block." ;
718+ const VERUS_SPEC_WARNING_END : & str =
719+ "= note: this warning originates in the attribute macro `verus_spec`" ;
720+
721+ fn run_verify_command ( cmd : & mut Command ) -> std:: io:: Result < std:: process:: ExitStatus > {
722+ let configured_color = std:: env:: var_os ( "CARGO_TERM_COLOR" ) ;
723+ if should_force_cargo_color ( std:: io:: stderr ( ) . is_terminal ( ) , configured_color. as_deref ( ) ) {
724+ // Piping stderr for filtering would otherwise make Cargo disable the
725+ // colors it normally emits to an interactive terminal.
726+ cmd. env ( "CARGO_TERM_COLOR" , "always" ) ;
727+ }
728+ cmd. stderr ( Stdio :: piped ( ) ) ;
729+ let mut child = cmd. spawn ( ) ?;
730+ let child_stderr = child. stderr . take ( ) . ok_or_else ( || {
731+ std:: io:: Error :: new (
732+ std:: io:: ErrorKind :: Other ,
733+ "could not capture the verification process stderr" ,
734+ )
735+ } ) ?;
736+
737+ let filter_result =
738+ filter_verus_spec_warnings ( BufReader :: new ( child_stderr) , & mut std:: io:: stderr ( ) . lock ( ) ) ;
739+ let status_result = child. wait ( ) ;
740+
741+ filter_result?;
742+ status_result
743+ }
744+
745+ fn should_force_cargo_color ( stderr_is_terminal : bool , configured : Option < & OsStr > ) -> bool {
746+ stderr_is_terminal
747+ && configured
748+ . map ( |value| value. eq_ignore_ascii_case ( "auto" ) )
749+ . unwrap_or ( true )
750+ }
751+
752+ fn filter_verus_spec_warnings < R : BufRead , W : Write > (
753+ reader : R ,
754+ writer : & mut W ,
755+ ) -> std:: io:: Result < ( ) > {
756+ let mut candidate = Vec :: new ( ) ;
757+ let mut suppress_following_blank_line = false ;
758+
759+ for line in reader. lines ( ) {
760+ let line = line?;
761+ let plain_line = strip_ansi_escape_codes ( & line) ;
762+
763+ if suppress_following_blank_line {
764+ suppress_following_blank_line = false ;
765+ if plain_line. trim ( ) . is_empty ( ) {
766+ continue ;
767+ }
768+ }
769+
770+ if !candidate. is_empty ( ) {
771+ let is_warning_end = plain_line. contains ( VERUS_SPEC_WARNING_END ) ;
772+ candidate. push ( line) ;
773+ if is_warning_end {
774+ candidate. clear ( ) ;
775+ suppress_following_blank_line = true ;
776+ }
777+ continue ;
778+ }
779+
780+ if plain_line. contains ( VERUS_SPEC_WARNING_START ) {
781+ candidate. push ( line) ;
782+ } else {
783+ writeln ! ( writer, "{line}" ) ?;
784+ }
785+ }
786+
787+ // A truncated or changed diagnostic is not a confirmed match. Preserve it
788+ // instead of accidentally hiding unrelated compiler output.
789+ for line in candidate {
790+ writeln ! ( writer, "{line}" ) ?;
791+ }
792+ writer. flush ( )
793+ }
794+
795+ fn strip_ansi_escape_codes ( line : & str ) -> String {
796+ let mut plain = String :: with_capacity ( line. len ( ) ) ;
797+ let mut chars = line. chars ( ) . peekable ( ) ;
798+
799+ while let Some ( ch) = chars. next ( ) {
800+ if ch == '\u{1b}' && chars. next_if_eq ( & '[' ) . is_some ( ) {
801+ for code in chars. by_ref ( ) {
802+ if ( '@' ..='~' ) . contains ( & code) {
803+ break ;
804+ }
805+ }
806+ } else {
807+ plain. push ( ch) ;
808+ }
809+ }
810+
811+ plain
812+ }
813+
715814fn verus_args_should_apply_to_roots_only ( args : & [ String ] ) -> bool {
716815 args. iter ( ) . any ( |arg| {
717816 matches ! (
0 commit comments