@@ -458,6 +458,54 @@ pub struct ClippyResponse {
458458 pub exit_detail : String ,
459459}
460460
461+ #[ derive( Debug , Clone ) ]
462+ pub struct MiriRequest {
463+ pub channel : Channel ,
464+ pub crate_type : CrateType ,
465+ pub edition : Edition ,
466+ pub code : String ,
467+ }
468+
469+ impl MiriRequest {
470+ pub ( crate ) fn delete_previous_main_request ( & self ) -> DeleteFileRequest {
471+ delete_previous_primary_file_request ( self . crate_type )
472+ }
473+
474+ pub ( crate ) fn write_main_request ( & self ) -> WriteFileRequest {
475+ write_primary_file_request ( self . crate_type , & self . code )
476+ }
477+
478+ pub ( crate ) fn execute_cargo_request ( & self ) -> ExecuteCommandRequest {
479+ ExecuteCommandRequest {
480+ cmd : "cargo" . to_owned ( ) ,
481+ args : vec ! [ "miri-playground" . to_owned( ) ] ,
482+ envs : Default :: default ( ) ,
483+ cwd : None ,
484+ }
485+ }
486+ }
487+
488+ impl CargoTomlModifier for MiriRequest {
489+ fn modify_cargo_toml ( & self , mut cargo_toml : toml:: Value ) -> toml:: Value {
490+ if self . edition == Edition :: Rust2024 {
491+ cargo_toml = modify_cargo_toml:: set_feature_edition2024 ( cargo_toml) ;
492+ }
493+
494+ cargo_toml = modify_cargo_toml:: set_edition ( cargo_toml, self . edition . to_cargo_toml_key ( ) ) ;
495+
496+ if let Some ( crate_type) = self . crate_type . to_library_cargo_toml_key ( ) {
497+ cargo_toml = modify_cargo_toml:: set_crate_type ( cargo_toml, crate_type) ;
498+ }
499+ cargo_toml
500+ }
501+ }
502+
503+ #[ derive( Debug , Clone ) ]
504+ pub struct MiriResponse {
505+ pub success : bool ,
506+ pub exit_detail : String ,
507+ }
508+
461509#[ derive( Debug , Clone ) ]
462510pub struct WithOutput < T > {
463511 pub response : T ,
@@ -649,6 +697,30 @@ where
649697 . await
650698 }
651699
700+ pub async fn miri ( & self , request : MiriRequest ) -> Result < WithOutput < MiriResponse > , MiriError > {
701+ use miri_error:: * ;
702+
703+ self . select_channel ( request. channel )
704+ . await
705+ . context ( CouldNotStartContainerSnafu ) ?
706+ . miri ( request)
707+ . await
708+ }
709+
710+ pub async fn begin_miri (
711+ & self ,
712+ token : CancellationToken ,
713+ request : MiriRequest ,
714+ ) -> Result < ActiveMiri , MiriError > {
715+ use miri_error:: * ;
716+
717+ self . select_channel ( request. channel )
718+ . await
719+ . context ( CouldNotStartContainerSnafu ) ?
720+ . begin_miri ( token, request)
721+ . await
722+ }
723+
652724 pub async fn idle ( & mut self ) -> Result < ( ) > {
653725 let Self {
654726 stable,
@@ -1076,6 +1148,75 @@ impl Container {
10761148 } )
10771149 }
10781150
1151+ async fn miri ( & self , request : MiriRequest ) -> Result < WithOutput < MiriResponse > , MiriError > {
1152+ let token = Default :: default ( ) ;
1153+
1154+ let ActiveMiri {
1155+ task,
1156+ stdout_rx,
1157+ stderr_rx,
1158+ } = self . begin_miri ( token, request) . await ?;
1159+
1160+ WithOutput :: try_absorb ( task, stdout_rx, stderr_rx) . await
1161+ }
1162+
1163+ async fn begin_miri (
1164+ & self ,
1165+ token : CancellationToken ,
1166+ request : MiriRequest ,
1167+ ) -> Result < ActiveMiri , MiriError > {
1168+ use miri_error:: * ;
1169+
1170+ let delete_previous_main = request. delete_previous_main_request ( ) ;
1171+ let write_main = request. write_main_request ( ) ;
1172+ let execute_cargo = request. execute_cargo_request ( ) ;
1173+
1174+ let delete_previous_main = self . commander . one ( delete_previous_main) ;
1175+ let write_main = self . commander . one ( write_main) ;
1176+ let modify_cargo_toml = self . modify_cargo_toml . modify_for ( & request) ;
1177+
1178+ let ( delete_previous_main, write_main, modify_cargo_toml) =
1179+ join ! ( delete_previous_main, write_main, modify_cargo_toml) ;
1180+
1181+ delete_previous_main. context ( CouldNotDeletePreviousCodeSnafu ) ?;
1182+ write_main. context ( CouldNotWriteCodeSnafu ) ?;
1183+ modify_cargo_toml. context ( CouldNotModifyCargoTomlSnafu ) ?;
1184+
1185+ let SpawnCargo {
1186+ task,
1187+ stdin_tx,
1188+ stdout_rx,
1189+ stderr_rx,
1190+ } = self
1191+ . spawn_cargo_task ( token, execute_cargo)
1192+ . await
1193+ . context ( CouldNotStartCargoSnafu ) ?;
1194+
1195+ drop ( stdin_tx) ;
1196+
1197+ let task = async move {
1198+ let ExecuteCommandResponse {
1199+ success,
1200+ exit_detail,
1201+ } = task
1202+ . await
1203+ . context ( CargoTaskPanickedSnafu ) ?
1204+ . context ( CargoFailedSnafu ) ?;
1205+
1206+ Ok ( MiriResponse {
1207+ success,
1208+ exit_detail,
1209+ } )
1210+ }
1211+ . boxed ( ) ;
1212+
1213+ Ok ( ActiveMiri {
1214+ task,
1215+ stdout_rx,
1216+ stderr_rx,
1217+ } )
1218+ }
1219+
10791220 async fn spawn_cargo_task (
10801221 & self ,
10811222 token : CancellationToken ,
@@ -1346,6 +1487,47 @@ pub enum ClippyError {
13461487 CargoFailed { source : SpawnCargoError } ,
13471488}
13481489
1490+ pub struct ActiveMiri {
1491+ pub task : BoxFuture < ' static , Result < MiriResponse , MiriError > > ,
1492+ pub stdout_rx : mpsc:: Receiver < String > ,
1493+ pub stderr_rx : mpsc:: Receiver < String > ,
1494+ }
1495+
1496+ impl fmt:: Debug for ActiveMiri {
1497+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1498+ f. debug_struct ( "ActiveMiri" )
1499+ . field ( "task" , & "<future>" )
1500+ . field ( "stdout_rx" , & self . stdout_rx )
1501+ . field ( "stderr_rx" , & self . stderr_rx )
1502+ . finish ( )
1503+ }
1504+ }
1505+
1506+ #[ derive( Debug , Snafu ) ]
1507+ #[ snafu( module) ]
1508+ pub enum MiriError {
1509+ #[ snafu( display( "Could not start the container" ) ) ]
1510+ CouldNotStartContainer { source : Error } ,
1511+
1512+ #[ snafu( display( "Could not modify Cargo.toml" ) ) ]
1513+ CouldNotModifyCargoToml { source : ModifyCargoTomlError } ,
1514+
1515+ #[ snafu( display( "Could not delete previous source code" ) ) ]
1516+ CouldNotDeletePreviousCode { source : CommanderError } ,
1517+
1518+ #[ snafu( display( "Could not write source code" ) ) ]
1519+ CouldNotWriteCode { source : CommanderError } ,
1520+
1521+ #[ snafu( display( "Could not start Cargo task" ) ) ]
1522+ CouldNotStartCargo { source : SpawnCargoError } ,
1523+
1524+ #[ snafu( display( "The Cargo task panicked" ) ) ]
1525+ CargoTaskPanicked { source : tokio:: task:: JoinError } ,
1526+
1527+ #[ snafu( display( "Cargo task failed" ) ) ]
1528+ CargoFailed { source : SpawnCargoError } ,
1529+ }
1530+
13491531struct SpawnCargo {
13501532 task : JoinHandle < Result < ExecuteCommandResponse , SpawnCargoError > > ,
13511533 stdin_tx : mpsc:: Sender < String > ,
@@ -2910,6 +3092,43 @@ mod tests {
29103092 Ok ( ( ) )
29113093 }
29123094
3095+ const ARBITRARY_MIRI_REQUEST : MiriRequest = MiriRequest {
3096+ channel : Channel :: Nightly ,
3097+ crate_type : CrateType :: Binary ,
3098+ edition : Edition :: Rust2021 ,
3099+ code : String :: new ( ) ,
3100+ } ;
3101+
3102+ #[ tokio:: test]
3103+ #[ snafu:: report]
3104+ async fn miri ( ) -> Result < ( ) > {
3105+ // cargo-miri-playground only exists inside the container
3106+ let coordinator = new_coordinator_docker ( ) . await ;
3107+
3108+ let req = MiriRequest {
3109+ code : r#"
3110+ fn main() {
3111+ let mut a: [u8; 0] = [];
3112+ unsafe { *a.get_unchecked_mut(1) = 1; }
3113+ }
3114+ "#
3115+ . into ( ) ,
3116+ ..ARBITRARY_MIRI_REQUEST
3117+ } ;
3118+
3119+ let response = coordinator. miri ( req) . with_timeout ( ) . await . unwrap ( ) ;
3120+
3121+ assert ! ( !response. success, "stderr: {}" , response. stderr) ;
3122+
3123+ assert_contains ! ( response. stderr, "Undefined Behavior" ) ;
3124+ assert_contains ! ( response. stderr, "pointer to 1 byte" ) ;
3125+ assert_contains ! ( response. stderr, "starting at offset 0" ) ;
3126+ assert_contains ! ( response. stderr, "is out-of-bounds" ) ;
3127+ assert_contains ! ( response. stderr, "has size 0" ) ;
3128+
3129+ Ok ( ( ) )
3130+ }
3131+
29133132 // The next set of tests are broader than the functionality of a
29143133 // single operation.
29153134
0 commit comments