Skip to content

Commit

Permalink
Merge pull request #1002 from rust-lang/miri
Browse files Browse the repository at this point in the history
Install and prepare Miri in the primary container
  • Loading branch information
shepmaster authored Nov 29, 2023
2 parents f14e3a8 + c9a730c commit dde4408
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 180 deletions.
8 changes: 6 additions & 2 deletions compiler/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- \
--default-toolchain "${channel}" \
--target wasm32-unknown-unknown \
--component rustfmt \
--component clippy
--component clippy \
--component rust-src
RUN if [ "${channel}" = 'nightly' ]; then rustup component add miri; fi

COPY --chown=playground entrypoint.sh /playground/tools/

Expand Down Expand Up @@ -111,11 +113,13 @@ ARG channel
RUN cargo build
RUN cargo build --release
RUN cargo clippy
RUN if [ "${channel}" = 'nightly' ]; then cargo miri setup; cargo miri run; fi
RUN rm src/*.rs

COPY --from=modify-cargo-toml /playground/modify-cargo-toml/target/release/modify-cargo-toml /playground/.cargo/bin
COPY --from=build-orchestrator /playground/.cargo/bin/worker /playground/.cargo/bin/worker
COPY --from=wasm-tools /playground/.cargo/bin/wasm-tools /playground/.cargo/bin
COPY --chown=playground cargo-wasm /playground/.cargo/bin/
COPY --chown=playground cargo-wasm /playground/.cargo/bin
COPY --chown=playground cargo-miri-playground /playground/.cargo/bin

ENTRYPOINT ["/playground/tools/entrypoint.sh"]
7 changes: 7 additions & 0 deletions compiler/base/cargo-miri-playground
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

set -eu

export MIRI_SYSROOT=~/.cache/miri
export MIRIFLAGS="-Zmiri-disable-isolation"
exec cargo miri run
219 changes: 219 additions & 0 deletions compiler/base/orchestrator/src/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,54 @@ pub struct ClippyResponse {
pub exit_detail: String,
}

#[derive(Debug, Clone)]
pub struct MiriRequest {
pub channel: Channel,
pub crate_type: CrateType,
pub edition: Edition,
pub code: String,
}

impl MiriRequest {
pub(crate) fn delete_previous_main_request(&self) -> DeleteFileRequest {
delete_previous_primary_file_request(self.crate_type)
}

pub(crate) fn write_main_request(&self) -> WriteFileRequest {
write_primary_file_request(self.crate_type, &self.code)
}

pub(crate) fn execute_cargo_request(&self) -> ExecuteCommandRequest {
ExecuteCommandRequest {
cmd: "cargo".to_owned(),
args: vec!["miri-playground".to_owned()],
envs: Default::default(),
cwd: None,
}
}
}

impl CargoTomlModifier for MiriRequest {
fn modify_cargo_toml(&self, mut cargo_toml: toml::Value) -> toml::Value {
if self.edition == Edition::Rust2024 {
cargo_toml = modify_cargo_toml::set_feature_edition2024(cargo_toml);
}

cargo_toml = modify_cargo_toml::set_edition(cargo_toml, self.edition.to_cargo_toml_key());

if let Some(crate_type) = self.crate_type.to_library_cargo_toml_key() {
cargo_toml = modify_cargo_toml::set_crate_type(cargo_toml, crate_type);
}
cargo_toml
}
}

#[derive(Debug, Clone)]
pub struct MiriResponse {
pub success: bool,
pub exit_detail: String,
}

#[derive(Debug, Clone)]
pub struct WithOutput<T> {
pub response: T,
Expand Down Expand Up @@ -649,6 +697,30 @@ where
.await
}

pub async fn miri(&self, request: MiriRequest) -> Result<WithOutput<MiriResponse>, MiriError> {
use miri_error::*;

self.select_channel(request.channel)
.await
.context(CouldNotStartContainerSnafu)?
.miri(request)
.await
}

pub async fn begin_miri(
&self,
token: CancellationToken,
request: MiriRequest,
) -> Result<ActiveMiri, MiriError> {
use miri_error::*;

self.select_channel(request.channel)
.await
.context(CouldNotStartContainerSnafu)?
.begin_miri(token, request)
.await
}

pub async fn idle(&mut self) -> Result<()> {
let Self {
stable,
Expand Down Expand Up @@ -1076,6 +1148,75 @@ impl Container {
})
}

async fn miri(&self, request: MiriRequest) -> Result<WithOutput<MiriResponse>, MiriError> {
let token = Default::default();

let ActiveMiri {
task,
stdout_rx,
stderr_rx,
} = self.begin_miri(token, request).await?;

WithOutput::try_absorb(task, stdout_rx, stderr_rx).await
}

async fn begin_miri(
&self,
token: CancellationToken,
request: MiriRequest,
) -> Result<ActiveMiri, MiriError> {
use miri_error::*;

let delete_previous_main = request.delete_previous_main_request();
let write_main = request.write_main_request();
let execute_cargo = request.execute_cargo_request();

let delete_previous_main = self.commander.one(delete_previous_main);
let write_main = self.commander.one(write_main);
let modify_cargo_toml = self.modify_cargo_toml.modify_for(&request);

let (delete_previous_main, write_main, modify_cargo_toml) =
join!(delete_previous_main, write_main, modify_cargo_toml);

delete_previous_main.context(CouldNotDeletePreviousCodeSnafu)?;
write_main.context(CouldNotWriteCodeSnafu)?;
modify_cargo_toml.context(CouldNotModifyCargoTomlSnafu)?;

let SpawnCargo {
task,
stdin_tx,
stdout_rx,
stderr_rx,
} = self
.spawn_cargo_task(token, execute_cargo)
.await
.context(CouldNotStartCargoSnafu)?;

drop(stdin_tx);

let task = async move {
let ExecuteCommandResponse {
success,
exit_detail,
} = task
.await
.context(CargoTaskPanickedSnafu)?
.context(CargoFailedSnafu)?;

Ok(MiriResponse {
success,
exit_detail,
})
}
.boxed();

Ok(ActiveMiri {
task,
stdout_rx,
stderr_rx,
})
}

async fn spawn_cargo_task(
&self,
token: CancellationToken,
Expand Down Expand Up @@ -1346,6 +1487,47 @@ pub enum ClippyError {
CargoFailed { source: SpawnCargoError },
}

pub struct ActiveMiri {
pub task: BoxFuture<'static, Result<MiriResponse, MiriError>>,
pub stdout_rx: mpsc::Receiver<String>,
pub stderr_rx: mpsc::Receiver<String>,
}

impl fmt::Debug for ActiveMiri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ActiveMiri")
.field("task", &"<future>")
.field("stdout_rx", &self.stdout_rx)
.field("stderr_rx", &self.stderr_rx)
.finish()
}
}

#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum MiriError {
#[snafu(display("Could not start the container"))]
CouldNotStartContainer { source: Error },

#[snafu(display("Could not modify Cargo.toml"))]
CouldNotModifyCargoToml { source: ModifyCargoTomlError },

#[snafu(display("Could not delete previous source code"))]
CouldNotDeletePreviousCode { source: CommanderError },

#[snafu(display("Could not write source code"))]
CouldNotWriteCode { source: CommanderError },

#[snafu(display("Could not start Cargo task"))]
CouldNotStartCargo { source: SpawnCargoError },

#[snafu(display("The Cargo task panicked"))]
CargoTaskPanicked { source: tokio::task::JoinError },

#[snafu(display("Cargo task failed"))]
CargoFailed { source: SpawnCargoError },
}

struct SpawnCargo {
task: JoinHandle<Result<ExecuteCommandResponse, SpawnCargoError>>,
stdin_tx: mpsc::Sender<String>,
Expand Down Expand Up @@ -2910,6 +3092,43 @@ mod tests {
Ok(())
}

const ARBITRARY_MIRI_REQUEST: MiriRequest = MiriRequest {
channel: Channel::Nightly,
crate_type: CrateType::Binary,
edition: Edition::Rust2021,
code: String::new(),
};

#[tokio::test]
#[snafu::report]
async fn miri() -> Result<()> {
// cargo-miri-playground only exists inside the container
let coordinator = new_coordinator_docker().await;

let req = MiriRequest {
code: r#"
fn main() {
let mut a: [u8; 0] = [];
unsafe { *a.get_unchecked_mut(1) = 1; }
}
"#
.into(),
..ARBITRARY_MIRI_REQUEST
};

let response = coordinator.miri(req).with_timeout().await.unwrap();

assert!(!response.success, "stderr: {}", response.stderr);

assert_contains!(response.stderr, "Undefined Behavior");
assert_contains!(response.stderr, "pointer to 1 byte");
assert_contains!(response.stderr, "starting at offset 0");
assert_contains!(response.stderr, "is out-of-bounds");
assert_contains!(response.stderr, "has size 0");

Ok(())
}

// The next set of tests are broader than the functionality of a
// single operation.

Expand Down
9 changes: 1 addition & 8 deletions compiler/miri/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
ARG base_image=shepmaster/rust-nightly
FROM ${base_image}

RUN rustup component add rust-src miri

RUN echo 'fn main() {}' > src/main.rs
RUN cargo miri setup
RUN cargo miri run
RUN rm src/*.rs

ADD --chown=playground cargo-miri-playground /playground/.cargo/bin
# The base image takes care of all this for now

ENTRYPOINT ["/playground/tools/entrypoint.sh"]
34 changes: 11 additions & 23 deletions ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,6 @@ enum Error {
SandboxCreation { source: sandbox::Error },
#[snafu(display("Expansion operation failed: {}", source))]
Expansion { source: sandbox::Error },
#[snafu(display("Interpreting operation failed: {}", source))]
Interpreting { source: sandbox::Error },
#[snafu(display("Caching operation failed: {}", source))]
Caching { source: sandbox::Error },
#[snafu(display("Gist creation failed: {}", source))]
Expand Down Expand Up @@ -207,6 +205,11 @@ enum Error {
source: server_axum::api_orchestrator_integration_impls::ParseClippyRequestError,
},

#[snafu(context(false))]
MiriRequest {
source: server_axum::api_orchestrator_integration_impls::ParseMiriRequestError,
},

// Remove at a later point. From here ...
#[snafu(display("The value {:?} is not a valid edition", value))]
InvalidEdition { value: String },
Expand Down Expand Up @@ -248,6 +251,11 @@ enum Error {
source: orchestrator::coordinator::ClippyError,
},

#[snafu(display("Unable to convert the Miri request"))]
Miri {
source: orchestrator::coordinator::MiriError,
},

#[snafu(display("The operation timed out"))]
Timeout { source: tokio::time::error::Elapsed },

Expand Down Expand Up @@ -377,6 +385,7 @@ struct MiriRequest {
#[derive(Debug, Clone, Serialize)]
struct MiriResponse {
success: bool,
exit_detail: String,
stdout: String,
stderr: String,
}
Expand Down Expand Up @@ -443,27 +452,6 @@ struct EvaluateResponse {
error: Option<String>,
}

impl TryFrom<MiriRequest> for sandbox::MiriRequest {
type Error = Error;

fn try_from(me: MiriRequest) -> Result<Self> {
Ok(sandbox::MiriRequest {
code: me.code,
edition: parse_edition(&me.edition)?,
})
}
}

impl From<sandbox::MiriResponse> for MiriResponse {
fn from(me: sandbox::MiriResponse) -> Self {
MiriResponse {
success: me.success,
stdout: me.stdout,
stderr: me.stderr,
}
}
}

impl TryFrom<MacroExpansionRequest> for sandbox::MacroExpansionRequest {
type Error = Error;

Expand Down
Loading

0 comments on commit dde4408

Please sign in to comment.