Skip to content

Add Miri as a tool #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
- env:
- TOOLS_TO_BUILD="clippy"
script: ./.travis/build-containers.sh || true
- env:
- TOOLS_TO_BUILD="miri"
script: ./.travis/build-containers.sh || true

- stage: compile code
env:
Expand Down
2 changes: 1 addition & 1 deletion compiler/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
set -euv -o pipefail

channels_to_build="${CHANNELS_TO_BUILD-stable beta nightly}"
tools_to_build="${TOOLS_TO_BUILD-rustfmt clippy}"
tools_to_build="${TOOLS_TO_BUILD-rustfmt clippy miri}"
perform_push="${PERFORM_PUSH-false}"

repository=shepmaster
Expand Down
4 changes: 2 additions & 2 deletions compiler/fetch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ set -euv -o pipefail

repository=shepmaster

for image in rust-stable rust-beta rust-nightly rustfmt clippy; do
for image in rust-stable rust-beta rust-nightly rustfmt clippy miri; do
docker pull "${repository}/${image}"
# The backend expects images without a respoitory prefix
# The backend expects images without a repository prefix
docker tag "${repository}/${image}" "${image}"
done
17 changes: 17 additions & 0 deletions compiler/miri/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM shepmaster/rust-nightly as sources

RUN rustup component add rust-src

FROM sources as build

RUN cargo install xargo
RUN git clone https://github.com/solson/miri ~/miri
RUN ~/miri/xargo/build.sh
RUN cargo install --all-features --path ~/miri

FROM sources

COPY --from=build /root/.xargo /root/.xargo
COPY --from=build /root/.cargo/bin/miri /root/.cargo/bin
COPY --from=build /root/.cargo/bin/cargo-miri /root/.cargo/bin
ADD cargo-miri-playground /root/.cargo/bin
6 changes: 6 additions & 0 deletions compiler/miri/cargo-miri-playground
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -eu

export MIRI_SYSROOT=~/.xargo/HOST
exec cargo miri -- -Zmiri-start-fn
18 changes: 18 additions & 0 deletions tests/spec/features/tools_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ def code_with_lint_warnings
EOF
end

scenario "sanitize code with Miri" do
editor.set code_with_undefined_behavior
in_tools_menu { click_on("Miri") }

within(".output-stderr") do
expect(page).to have_content /pointer computed at offset 1, outside bounds of allocation \d+ which has size 0/
end
end

def code_with_undefined_behavior
<<~EOF
fn main() {
let mut a: [u8; 0] = [];
unsafe { *a.get_unchecked_mut(1) = 1; }
}
EOF
end

def editor
Editor.new(page)
end
Expand Down
31 changes: 25 additions & 6 deletions ui/frontend/Help.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import integer32Logo from './assets/integer32-logo.svg';

const ACE_URL = 'https://github.com/ajaxorg/ace';
const CLIPPY_URL = 'https://github.com/Manishearth/rust-clippy';
const MIRI_URL = 'https://github.com/solson/miri';
const CRATES_IO_URL = 'https://crates.io/';
const RUST_COOKBOOK_URL = 'https://rust-lang-nursery.github.io/rust-cookbook/';
const CRATES_URL = 'https://github.com/integer32llc/rust-playground/blob/master/compiler/base/Cargo.toml';
Expand All @@ -35,6 +36,13 @@ const CLIPPY_EXAMPLE = `fn main() {
}
}`;

const MIRI_EXAMPLE = `fn main() {
let mut a: [u8; 0] = [];
unsafe {
*a.get_unchecked_mut(1) = 1;
}
}`;

const RUSTFMT_EXAMPLE = `// wow, this is ugly!
fn main ()
{ struct Foo { a: u8, b: String, }
Expand Down Expand Up @@ -116,6 +124,17 @@ const Help = ({ navigateToIndex }: HelpProps) => (
</p>
</LinkableSection>

<LinkableSection id="features-formatting" header="Formatting code" level={H3}>
<p>
<a href={RUSTFMT_URL}>rustfmt</a> is a tool for formatting Rust code
according to the Rust style guidelines. Click on the <strong>Format</strong>
{' '}
button in the <strong>Tools</strong> menu to automatically reformat your code.
</p>

<Example code={RUSTFMT_EXAMPLE} />
</LinkableSection>

<LinkableSection id="features-linting" header="Linting code" level={H3}>
<p>
<a href={CLIPPY_URL}>Clippy</a> is a collection of lints to catch common
Expand All @@ -128,15 +147,15 @@ const Help = ({ navigateToIndex }: HelpProps) => (
<Example code={CLIPPY_EXAMPLE} />
</LinkableSection>

<LinkableSection id="features-formatting" header="Formatting code" level={H3}>
<LinkableSection id="features-miri" header="Checking code for undefined behavior" level={H3}>
<p>
<a href={RUSTFMT_URL}>rustfmt</a> is a tool for formatting Rust code
according to the Rust style guidelines. Click on the <strong>Format</strong>
{' '}
button in the <strong>Tools</strong> menu to automatically reformat your code.
<a href={MIRI_URL}>Miri</a> is an interpreter for Rust's mid-level intermediate
representation (MIR) and can be used to detect certain kinds of undefined behavior
in your unsafe Rust code. Click on the <strong>Miri</strong> button in
the <strong>Tools</strong> menu to check.
</p>

<Example code={RUSTFMT_EXAMPLE} />
<Example code={MIRI_EXAMPLE} />
</LinkableSection>

<LinkableSection id="features-sharing" header="Sharing code" level={H3}>
Expand Down
9 changes: 8 additions & 1 deletion ui/frontend/Output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ interface FormatProps {
const Output: React.SFC<OutputProps> = ({
// https://github.com/palantir/tslint/issues/3960
// tslint:disable-next-line:trailing-comma
somethingToShow, meta: { focus }, execute, format, clippy, assembly, llvmIr, mir, wasm, gist, ...props
somethingToShow, meta: { focus }, execute, format, clippy, miri, assembly, llvmIr, mir, wasm, gist, ...props
}) => {
if (!somethingToShow) {
return null;
Expand All @@ -139,6 +139,7 @@ const Output: React.SFC<OutputProps> = ({
{focus === Focus.Execute && <SimplePane {...execute} kind="execute" />}
{focus === Focus.Format && <Format {...format} />}
{focus === Focus.Clippy && <SimplePane {...clippy} kind="clippy" />}
{focus === Focus.Miri && <SimplePane {...miri} kind="miri" />}
{focus === Focus.Asm && <PaneWithCode {...assembly} kind="asm" />}
{focus === Focus.LlvmIr && <PaneWithCode {...llvmIr} kind="llvm-ir" />}
{focus === Focus.Mir && <PaneWithCode {...mir} kind="mir" />}
Expand All @@ -163,6 +164,10 @@ const Output: React.SFC<OutputProps> = ({
label="Clippy"
onClick={props.focusClippy}
tabProps={clippy} />
<Tab kind={Focus.Miri} focus={focus}
label="Miri"
onClick={props.focusMiri}
tabProps={miri} />
<Tab kind={Focus.Asm} focus={focus}
label="ASM"
onClick={props.focusAssembly}
Expand Down Expand Up @@ -208,6 +213,7 @@ interface OutputProps extends OutputState {
focusExecute: () => void;
focusFormat: () => void;
focusClippy: () => void;
focusMiri: () => void;
focusAssembly: () => void;
focusLlvmIr: () => void;
focusMir: () => void;
Expand All @@ -225,6 +231,7 @@ const mapDispatchToProps = ({
focusExecute: () => changeFocus(Focus.Execute),
focusFormat: () => changeFocus(Focus.Format),
focusClippy: () => changeFocus(Focus.Clippy),
focusMiri: () => changeFocus(Focus.Miri),
focusAssembly: () => changeFocus(Focus.Asm),
focusLlvmIr: () => changeFocus(Focus.LlvmIr),
focusMir: () => changeFocus(Focus.Mir),
Expand Down
9 changes: 9 additions & 0 deletions ui/frontend/ToolsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import MenuGroup from './MenuGroup';
import {
performClippy,
performFormat,
performMiri,
} from './actions';
import State from './state';

interface ToolsMenuProps {
clippy: () => any;
miri: () => any;
format: () => any;
close: () => void;
}
Expand All @@ -28,11 +30,18 @@ const ToolsMenu: React.SFC<ToolsMenuProps> = props => (
onClick={() => { props.clippy(); props.close(); }}>
Catch common mistakes and improve the code using the Clippy linter.
</ButtonMenuItem>
<ButtonMenuItem
name="Miri"
onClick={() => { props.miri(); props.close(); }}>
Execute this program in the Miri interpreter to detect certain
cases of undefined behavior (like out-of-bounds memory access).
</ButtonMenuItem>
</MenuGroup>
);

const mapDispatchToProps = ({
clippy: performClippy,
miri: performMiri,
format: performFormat,
});

Expand Down
30 changes: 30 additions & 0 deletions ui/frontend/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const routes = {
execute: { pathname: '/execute' },
format: { pathname: '/format' },
clippy: { pathname: '/clippy' },
miri: { pathname: '/miri' },
meta: {
crates: { pathname: '/meta/crates' },
version: {
Expand Down Expand Up @@ -78,6 +79,9 @@ export enum ActionType {
RequestClippy = 'REQUEST_CLIPPY',
ClippySucceeded = 'CLIPPY_SUCCEEDED',
ClippyFailed = 'CLIPPY_FAILED',
RequestMiri = 'REQUEST_MIRI',
MiriSucceeded = 'MIRI_SUCCEEDED',
MiriFailed = 'MIRI_FAILED',
RequestGistLoad = 'REQUEST_GIST_LOAD',
GistLoadSucceeded = 'GIST_LOAD_SUCCEEDED',
GistLoadFailed = 'GIST_LOAD_FAILED',
Expand Down Expand Up @@ -387,6 +391,29 @@ export function performClippy(): ThunkAction {
};
}

const requestMiri = () =>
createAction(ActionType.RequestMiri);

const receiveMiriSuccess = ({ stdout, stderr }) =>
createAction(ActionType.MiriSucceeded, { stdout, stderr });

const receiveMiriFailure = ({ error }) =>
createAction(ActionType.MiriFailed, { error });

export function performMiri(): ThunkAction {
// TODO: Check a cache
return function(dispatch, getState) {
dispatch(requestMiri());

const { code } = getState();
const body = { code };

return jsonPost(routes.miri, body)
.then(json => dispatch(receiveMiriSuccess(json)))
.catch(json => dispatch(receiveMiriFailure(json)));
};
}

interface GistSuccessProps {
id: string;
url: string;
Expand Down Expand Up @@ -610,6 +637,9 @@ export type Action =
| ReturnType<typeof requestClippy>
| ReturnType<typeof receiveClippySuccess>
| ReturnType<typeof receiveClippyFailure>
| ReturnType<typeof requestMiri>
| ReturnType<typeof receiveMiriSuccess>
| ReturnType<typeof receiveMiriFailure>
| ReturnType<typeof requestGistLoad>
| ReturnType<typeof receiveGistLoadSuccess>
| ReturnType<typeof receiveGistLoadFailure>
Expand Down
2 changes: 2 additions & 0 deletions ui/frontend/reducers/output/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import gist from './gist';
import llvmIr from './llvmIr';
import meta from './meta';
import mir from './mir';
import miri from './miri';
import wasm from './wasm';

const output = combineReducers({
meta,
format,
clippy,
miri,
assembly,
llvmIr,
mir,
Expand Down
3 changes: 3 additions & 0 deletions ui/frontend/reducers/output/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export default function meta(state = DEFAULT, action: Action) {
case ActionType.RequestClippy:
return { ...state, focus: Focus.Clippy };

case ActionType.RequestMiri:
return { ...state, focus: Focus.Miri };

case ActionType.CompileLlvmIrRequest:
return { ...state, focus: Focus.LlvmIr };

Expand Down
31 changes: 31 additions & 0 deletions ui/frontend/reducers/output/miri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Action, ActionType } from '../../actions';
import { finish, start } from './sharedStateManagement';

const DEFAULT: State = {
requestsInProgress: 0,
stdout: null,
stderr: null,
error: null,
};

interface State {
requestsInProgress: number;
stdout?: string;
stderr?: string;
error?: string;
}

export default function miri(state = DEFAULT, action: Action) {
switch (action.type) {
case ActionType.RequestMiri:
return start(DEFAULT, state);
case ActionType.MiriSucceeded: {
const { stdout = '', stderr = '' } = action;
return finish(state, { stdout, stderr });
}
case ActionType.MiriFailed:
return finish(state, { error: action.error });
default:
return state;
}
}
1 change: 1 addition & 0 deletions ui/frontend/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const getOutputs = (state: State) => [
state.output.gist,
state.output.llvmIr,
state.output.mir,
state.output.miri,
state.output.wasm,
];

Expand Down
1 change: 1 addition & 0 deletions ui/frontend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export enum Backtrace {

export enum Focus {
Clippy = 'clippy',
Miri = 'miri',
LlvmIr = 'llvm-ir',
Mir = 'mir',
Wasm = 'wasm',
Expand Down
Loading