diff --git a/compiler/base/cargo-miri-playground b/compiler/base/cargo-miri-playground index ae6e3740..74f393ec 100755 --- a/compiler/base/cargo-miri-playground +++ b/compiler/base/cargo-miri-playground @@ -3,5 +3,5 @@ set -eu export MIRI_SYSROOT=~/.cache/miri -export MIRIFLAGS="-Zmiri-disable-isolation" +export MIRIFLAGS="${MIRIFLAGS:-} -Zmiri-disable-isolation" exec cargo miri run diff --git a/compiler/base/orchestrator/src/coordinator.rs b/compiler/base/orchestrator/src/coordinator.rs index 75352fcb..62f24e54 100644 --- a/compiler/base/orchestrator/src/coordinator.rs +++ b/compiler/base/orchestrator/src/coordinator.rs @@ -239,6 +239,12 @@ pub enum Channel { Nightly, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AliasingModel { + Stacked, + Tree, +} + impl Channel { #[cfg(test)] pub(crate) const ALL: [Self; 3] = [Self::Stable, Self::Beta, Self::Nightly]; @@ -654,6 +660,7 @@ pub struct MiriRequest { pub channel: Channel, pub crate_type: CrateType, pub edition: Edition, + pub aliasing_model: AliasingModel, pub code: String, } @@ -667,10 +674,18 @@ impl LowerRequest for MiriRequest { } fn execute_cargo_request(&self) -> ExecuteCommandRequest { + let mut miriflags = Vec::new(); + + if matches!(self.aliasing_model, AliasingModel::Tree) { + miriflags.push("-Zmiri-tree-borrows"); + } + + let miriflags = miriflags.join(" "); + ExecuteCommandRequest { cmd: "cargo".to_owned(), args: vec!["miri-playground".to_owned()], - envs: Default::default(), + envs: HashMap::from_iter([("MIRIFLAGS".to_owned(), miriflags)]), cwd: None, } } @@ -3939,6 +3954,7 @@ mod tests { channel: Channel::Nightly, crate_type: CrateType::Binary, edition: Edition::Rust2021, + aliasing_model: AliasingModel::Stacked, code: String::new(), }; diff --git a/compiler/build.sh b/compiler/build.sh index c7a4cb60..9f17daa3 100755 --- a/compiler/build.sh +++ b/compiler/build.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euv -o pipefail diff --git a/compiler/fetch.sh b/compiler/fetch.sh index ce455ff6..3b74b274 100755 --- a/compiler/fetch.sh +++ b/compiler/fetch.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euv -o pipefail diff --git a/tests/spec/features/backtrace_spec.rb b/tests/spec/features/backtrace_spec.rb index 33b94d57..b2909d5f 100644 --- a/tests/spec/features/backtrace_spec.rb +++ b/tests/spec/features/backtrace_spec.rb @@ -12,7 +12,7 @@ context "backtraces are enabled" do before do - in_advanced_options_menu { choose 'enabled' } + in_advanced_options_menu { within(:config_option, 'Backtrace') { choose 'On' } } within(:header) { click_on("Run") } end diff --git a/tests/spec/features/tools_spec.rb b/tests/spec/features/tools_spec.rb index e20252dd..0c1400f1 100644 --- a/tests/spec/features/tools_spec.rb +++ b/tests/spec/features/tools_spec.rb @@ -54,6 +54,30 @@ def code_with_undefined_behavior EOF end + scenario "configure Miri for tree borrows" do + editor.set code_valid_under_tree_borrows_but_not_stacked_borrows + in_advanced_options_menu { choose("tree") } + in_tools_menu { click_on("Miri") } + + within(:output, :stdout) do + expect(page).to have_content %r{[1, 2]}, wait: 10 + end + + within(:output, :stderr) do + expect(page).to_not have_content %r{Undefined Behavior} + end + end + + def code_valid_under_tree_borrows_but_not_stacked_borrows + <<~EOF + fn main() { + let val = [1u8, 2]; + let ptr = &val[0] as *const u8; + let _val = unsafe { *ptr.add(1) }; + } + EOF + end + scenario "expand macros with the nightly compiler" do editor.set code_that_uses_macros in_tools_menu { click_on("Expand macros") } diff --git a/tests/spec/spec_helper.rb b/tests/spec/spec_helper.rb index 49ac8219..e4bc3ba0 100644 --- a/tests/spec/spec_helper.rb +++ b/tests/spec/spec_helper.rb @@ -88,6 +88,12 @@ css { '[data-test-id = "notification"]' } end +Capybara.add_selector(:config_option) do + xpath do |label| + ".//div[span[contains(., '#{label}')]]" + end +end + RSpec.configure do |config| config.after(:example, :js) do page.execute_script <<~JS diff --git a/ui/frontend/.prettierignore b/ui/frontend/.prettierignore index 2b484bba..3be14a17 100644 --- a/ui/frontend/.prettierignore +++ b/ui/frontend/.prettierignore @@ -12,8 +12,10 @@ node_modules *.scss # Slowly migrate files that we've touched +!AdvancedOptionsMenu.tsx !BuildMenu.tsx !ButtonSet.tsx +!ConfigElement.tsx !Header.tsx !HelpExample.tsx !Notifications.tsx diff --git a/ui/frontend/AdvancedOptionsMenu.tsx b/ui/frontend/AdvancedOptionsMenu.tsx index 4e73ba6b..167aee57 100644 --- a/ui/frontend/AdvancedOptionsMenu.tsx +++ b/ui/frontend/AdvancedOptionsMenu.tsx @@ -1,46 +1,83 @@ import React, { useCallback } from 'react'; -import * as config from './reducers/configuration'; import { Either as EitherConfig, Select as SelectConfig } from './ConfigElement'; +import MenuAside from './MenuAside'; import MenuGroup from './MenuGroup'; -import * as selectors from './selectors'; -import { Backtrace, Edition } from './types'; import { useAppDispatch, useAppSelector } from './hooks'; +import * as config from './reducers/configuration'; +import * as selectors from './selectors'; +import { AliasingModel, Backtrace, Edition } from './types'; + +const MIRI_TREE_BORROWS_URL = 'https://github.com/rust-lang/miri#user-content--zmiri-tree-borrows'; + +const TreeBorrowAside: React.FC = () => ( + + Code that is accepted by Tree Borrows may be declared + undefined behavior in the future. + +); const AdvancedOptionsMenu: React.FC = () => { const isEditionDefault = useAppSelector(selectors.isEditionDefault); const edition = useAppSelector((state) => state.configuration.edition); - const isBacktraceSet = useAppSelector(selectors.getBacktraceSet); + const isBacktraceDefault = useAppSelector(selectors.isBacktraceDefault); const backtrace = useAppSelector((state) => state.configuration.backtrace); + const isAliasingModelDefault = useAppSelector(selectors.isAliasingModelDefault); + const aliasingModel = useAppSelector((state) => state.configuration.aliasingModel); const dispatch = useAppDispatch(); const changeEdition = useCallback((e: Edition) => dispatch(config.changeEdition(e)), [dispatch]); - const changeBacktrace = useCallback((b: Backtrace) => dispatch(config.changeBacktrace(b)), [dispatch]); + const changeBacktrace = useCallback( + (b: Backtrace) => dispatch(config.changeBacktrace(b)), + [dispatch], + ); + const changeAliasingModel = useCallback( + (b: AliasingModel) => dispatch(config.changeAliasingModel(b)), + [dispatch], + ); return ( - - - - - - - - - - + <> + + + + + + + + + + + + + } + /> + + ); }; diff --git a/ui/frontend/ConfigElement.tsx b/ui/frontend/ConfigElement.tsx index d3e1b2ad..02f5ce67 100644 --- a/ui/frontend/ConfigElement.tsx +++ b/ui/frontend/ConfigElement.tsx @@ -14,27 +14,39 @@ interface EitherProps extends ConfigElementProps { onChange: (_: T) => void; } -export const Either = - ({ id, a, b, aLabel = a, bLabel = b, value, onChange, ...rest }: EitherProps) => ( - -
- onChange(a as T)} /> - - onChange(b as T)} /> - -
-
- ); +export const Either = ({ + id, + a, + b, + aLabel = a, + bLabel = b, + value, + onChange, + ...rest +}: EitherProps) => ( + +
+ onChange(a as T)} + /> + + onChange(b as T)} + /> + +
+
+); interface SelectProps extends ConfigElementProps { children: React.ReactNode; @@ -42,9 +54,14 @@ interface SelectProps extends ConfigElementProps { onChange: (_: T) => void; } -export const Select = ({ value, onChange, children, ...rest }: SelectProps) => ( +export const Select = ({ + value, + onChange, + children, + ...rest +}: SelectProps) => ( - onChange(e.target.value as T)}> {children} @@ -53,18 +70,21 @@ export const Select = ({ value, onChange, children, ...rest } interface ConfigElementProps { children?: React.ReactNode; name: string; - isNotDefault?: boolean; - aside?: JSX.Element, + isDefault?: boolean; + aside?: JSX.Element; } -const ConfigElement: React.FC = ({ name, isNotDefault, aside, children }) => ( - -
- {name} -
- {children} +const ConfigElement: React.FC = ({ name, isDefault, aside, children }) => { + const actuallyDefault = isDefault ?? true; + const defaultStyle = actuallyDefault ? styles.name : styles.notDefault; + + return ( + +
+ {name} +
{children}
-
- {aside} - -); + {aside} + + ); +}; diff --git a/ui/frontend/reducers/configuration.ts b/ui/frontend/reducers/configuration.ts index 37752074..23fb9b4c 100644 --- a/ui/frontend/reducers/configuration.ts +++ b/ui/frontend/reducers/configuration.ts @@ -1,6 +1,7 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { + AliasingModel, AssemblyFlavor, Backtrace, Channel, @@ -36,6 +37,7 @@ interface State { mode: Mode; edition: Edition; backtrace: Backtrace; + aliasingModel: AliasingModel; } const initialState: State = { @@ -58,6 +60,7 @@ const initialState: State = { mode: Mode.Debug, edition: Edition.Rust2024, backtrace: Backtrace.Disabled, + aliasingModel: AliasingModel.Stacked, }; const slice = createSlice({ @@ -76,6 +79,10 @@ const slice = createSlice({ state.backtrace = action.payload; }, + changeAliasingModel: (state, action: PayloadAction) => { + state.aliasingModel = action.payload; + }, + changeChannel: (state, action: PayloadAction) => { state.channel = action.payload; }, @@ -146,6 +153,7 @@ export const { changeAceTheme, changeAssemblyFlavor, changeBacktrace, + changeAliasingModel, changeChannel, changeDemangleAssembly, changeEdition, diff --git a/ui/frontend/reducers/output/miri.ts b/ui/frontend/reducers/output/miri.ts index 0c52f12d..0ba94e84 100644 --- a/ui/frontend/reducers/output/miri.ts +++ b/ui/frontend/reducers/output/miri.ts @@ -4,6 +4,7 @@ import * as z from 'zod'; import { jsonPost, routes } from '../../api'; import { State as RootState } from '../../reducers'; import { miriRequestSelector } from '../../selectors'; +import { AliasingModel } from '../../types'; const sliceName = 'output/miri'; @@ -21,6 +22,7 @@ interface State { interface MiriRequestBody { code: string; edition: string; + aliasingModel: AliasingModel; } const MiriResponseBody = z.object({ diff --git a/ui/frontend/selectors/index.ts b/ui/frontend/selectors/index.ts index 09805f1a..69b93912 100644 --- a/ui/frontend/selectors/index.ts +++ b/ui/frontend/selectors/index.ts @@ -6,6 +6,7 @@ import { Backtrace, Channel, Edition, + AliasingModel, Focus, Orientation, PrimaryActionAuto, @@ -147,6 +148,7 @@ export const rustfmtVersionDetailsText = createSelector(getRustfmt, versionDetai export const miriVersionDetailsText = createSelector(getMiri, versionDetails); const editionSelector = (state: State) => state.configuration.edition; +export const aliasingModelSelector = (state: State) => state.configuration.aliasingModel; export const isNightlyChannel = createSelector( channelSelector, @@ -172,18 +174,23 @@ export const getChannelLabel = createSelector(channelSelector, (channel) => `${c export const isEditionDefault = createSelector( editionSelector, - edition => edition == Edition.Rust2024, + edition => edition === Edition.Rust2024, ); -export const getBacktraceSet = (state: State) => ( - state.configuration.backtrace !== Backtrace.Disabled +export const isBacktraceDefault = (state: State) => ( + state.configuration.backtrace === Backtrace.Disabled +); + +export const getBacktraceSet = createSelector(isBacktraceDefault, (b) => !b); + +export const isAliasingModelDefault = createSelector( + aliasingModelSelector, + aliasingModel => aliasingModel == AliasingModel.Stacked, ); export const getAdvancedOptionsSet = createSelector( - isEditionDefault, getBacktraceSet, - (editionDefault, backtraceSet) => ( - !editionDefault || backtraceSet - ), + isEditionDefault, isBacktraceDefault, isAliasingModelDefault, + (...areDefault) => !areDefault.every(n => n), ); export const hasProperties = (obj: object) => Object.values(obj).some(val => !!val); @@ -391,7 +398,8 @@ export const formatRequestSelector = createSelector( export const miriRequestSelector = createSelector( editionSelector, codeSelector, - (edition, code) => ({ edition, code }), + aliasingModelSelector, + (edition, code, aliasingModel) => ({ edition, code, aliasingModel }), ); export const macroExpansionRequestSelector = createSelector( diff --git a/ui/frontend/types.ts b/ui/frontend/types.ts index 4b49511c..eb06e7ab 100644 --- a/ui/frontend/types.ts +++ b/ui/frontend/types.ts @@ -150,6 +150,11 @@ export enum Backtrace { Enabled = 'enabled', } +export enum AliasingModel { + Stacked = 'stacked', + Tree = 'tree', +} + export enum Focus { Clippy = 'clippy', Miri = 'miri', diff --git a/ui/src/metrics.rs b/ui/src/metrics.rs index b1d7ab9a..973e5d4e 100644 --- a/ui/src/metrics.rs +++ b/ui/src/metrics.rs @@ -361,6 +361,7 @@ impl HasLabelsCore for coordinator::MiriRequest { channel, crate_type, edition, + aliasing_model: _, code: _, } = *self; diff --git a/ui/src/public_http_api.rs b/ui/src/public_http_api.rs index 6f6a32b4..6ca71b9f 100644 --- a/ui/src/public_http_api.rs +++ b/ui/src/public_http_api.rs @@ -103,6 +103,8 @@ pub(crate) struct MiriRequest { pub(crate) code: String, #[serde(default)] pub(crate) edition: String, + #[serde(default, rename = "aliasingModel")] + pub(crate) aliasing_model: Option, } #[derive(Debug, Clone, Serialize)] diff --git a/ui/src/server_axum.rs b/ui/src/server_axum.rs index ec65ba46..f4d38393 100644 --- a/ui/src/server_axum.rs +++ b/ui/src/server_axum.rs @@ -1285,12 +1285,22 @@ pub(crate) mod api_orchestrator_integration_impls { type Error = ParseMiriRequestError; fn try_from(other: api::MiriRequest) -> std::result::Result { - let api::MiriRequest { code, edition } = other; + let api::MiriRequest { + code, + edition, + aliasing_model, + } = other; + + let aliasing_model = match aliasing_model { + Some(am) => parse_aliasing_model(&am)?, + None => AliasingModel::Stacked, + }; Ok(MiriRequest { channel: Channel::Nightly, // TODO: use what user has submitted crate_type: CrateType::Binary, // TODO: use what user has submitted edition: parse_edition(&edition)?, + aliasing_model, code, }) } @@ -1300,6 +1310,8 @@ pub(crate) mod api_orchestrator_integration_impls { pub(crate) enum ParseMiriRequestError { #[snafu(transparent)] Edition { source: ParseEditionError }, + #[snafu(transparent)] + AliasingMode { source: ParseAliasingModelError }, } impl From> for api::MiriResponse { @@ -1521,6 +1533,20 @@ pub(crate) mod api_orchestrator_integration_impls { value: String, } + pub(crate) fn parse_aliasing_model(s: &str) -> Result { + Ok(match s { + "stacked" => AliasingModel::Stacked, + "tree" => AliasingModel::Tree, + value => return ParseAliasingModelSnafu { value }.fail(), + }) + } + + #[derive(Debug, Snafu)] + #[snafu(display("'{value}' is not a valid aliasing model"))] + pub(crate) struct ParseAliasingModelError { + value: String, + } + impl From for api::MetaGistResponse { fn from(me: gist::Gist) -> Self { api::MetaGistResponse {