Skip to content

Commit fde1c74

Browse files
authored
Merge pull request #372 from integer32llc/miri
Add Miri as a tool
2 parents 3cc6713 + 4d9975b commit fde1c74

File tree

17 files changed

+254
-10
lines changed

17 files changed

+254
-10
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
- env:
3939
- TOOLS_TO_BUILD="clippy"
4040
script: ./.travis/build-containers.sh || true
41+
- env:
42+
- TOOLS_TO_BUILD="miri"
43+
script: ./.travis/build-containers.sh || true
4144

4245
- stage: compile code
4346
env:

compiler/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
set -euv -o pipefail
44

55
channels_to_build="${CHANNELS_TO_BUILD-stable beta nightly}"
6-
tools_to_build="${TOOLS_TO_BUILD-rustfmt clippy}"
6+
tools_to_build="${TOOLS_TO_BUILD-rustfmt clippy miri}"
77
perform_push="${PERFORM_PUSH-false}"
88

99
repository=shepmaster

compiler/fetch.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ set -euv -o pipefail
44

55
repository=shepmaster
66

7-
for image in rust-stable rust-beta rust-nightly rustfmt clippy; do
7+
for image in rust-stable rust-beta rust-nightly rustfmt clippy miri; do
88
docker pull "${repository}/${image}"
9-
# The backend expects images without a respoitory prefix
9+
# The backend expects images without a repository prefix
1010
docker tag "${repository}/${image}" "${image}"
1111
done

compiler/miri/Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM shepmaster/rust-nightly as sources
2+
3+
RUN rustup component add rust-src
4+
5+
FROM sources as build
6+
7+
RUN cargo install xargo
8+
RUN git clone https://github.com/solson/miri ~/miri
9+
RUN ~/miri/xargo/build.sh
10+
RUN cargo install --all-features --path ~/miri
11+
12+
FROM sources
13+
14+
COPY --from=build /root/.xargo /root/.xargo
15+
COPY --from=build /root/.cargo/bin/miri /root/.cargo/bin
16+
COPY --from=build /root/.cargo/bin/cargo-miri /root/.cargo/bin
17+
ADD cargo-miri-playground /root/.cargo/bin

compiler/miri/cargo-miri-playground

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
3+
set -eu
4+
5+
export MIRI_SYSROOT=~/.xargo/HOST
6+
exec cargo miri -- -Zmiri-start-fn

tests/spec/features/tools_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ def code_with_lint_warnings
3737
EOF
3838
end
3939

40+
scenario "sanitize code with Miri" do
41+
editor.set code_with_undefined_behavior
42+
in_tools_menu { click_on("Miri") }
43+
44+
within(".output-stderr") do
45+
expect(page).to have_content /pointer computed at offset 1, outside bounds of allocation \d+ which has size 0/
46+
end
47+
end
48+
49+
def code_with_undefined_behavior
50+
<<~EOF
51+
fn main() {
52+
let mut a: [u8; 0] = [];
53+
unsafe { *a.get_unchecked_mut(1) = 1; }
54+
}
55+
EOF
56+
end
57+
4058
def editor
4159
Editor.new(page)
4260
end

ui/frontend/Help.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import integer32Logo from './assets/integer32-logo.svg';
99

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

39+
const MIRI_EXAMPLE = `fn main() {
40+
let mut a: [u8; 0] = [];
41+
unsafe {
42+
*a.get_unchecked_mut(1) = 1;
43+
}
44+
}`;
45+
3846
const RUSTFMT_EXAMPLE = `// wow, this is ugly!
3947
fn main ()
4048
{ struct Foo { a: u8, b: String, }
@@ -116,6 +124,17 @@ const Help = ({ navigateToIndex }: HelpProps) => (
116124
</p>
117125
</LinkableSection>
118126

127+
<LinkableSection id="features-formatting" header="Formatting code" level={H3}>
128+
<p>
129+
<a href={RUSTFMT_URL}>rustfmt</a> is a tool for formatting Rust code
130+
according to the Rust style guidelines. Click on the <strong>Format</strong>
131+
{' '}
132+
button in the <strong>Tools</strong> menu to automatically reformat your code.
133+
</p>
134+
135+
<Example code={RUSTFMT_EXAMPLE} />
136+
</LinkableSection>
137+
119138
<LinkableSection id="features-linting" header="Linting code" level={H3}>
120139
<p>
121140
<a href={CLIPPY_URL}>Clippy</a> is a collection of lints to catch common
@@ -128,15 +147,15 @@ const Help = ({ navigateToIndex }: HelpProps) => (
128147
<Example code={CLIPPY_EXAMPLE} />
129148
</LinkableSection>
130149

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

139-
<Example code={RUSTFMT_EXAMPLE} />
158+
<Example code={MIRI_EXAMPLE} />
140159
</LinkableSection>
141160

142161
<LinkableSection id="features-sharing" header="Sharing code" level={H3}>

ui/frontend/Output.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ interface FormatProps {
120120
const Output: React.SFC<OutputProps> = ({
121121
// https://github.com/palantir/tslint/issues/3960
122122
// tslint:disable-next-line:trailing-comma
123-
somethingToShow, meta: { focus }, execute, format, clippy, assembly, llvmIr, mir, wasm, gist, ...props
123+
somethingToShow, meta: { focus }, execute, format, clippy, miri, assembly, llvmIr, mir, wasm, gist, ...props
124124
}) => {
125125
if (!somethingToShow) {
126126
return null;
@@ -139,6 +139,7 @@ const Output: React.SFC<OutputProps> = ({
139139
{focus === Focus.Execute && <SimplePane {...execute} kind="execute" />}
140140
{focus === Focus.Format && <Format {...format} />}
141141
{focus === Focus.Clippy && <SimplePane {...clippy} kind="clippy" />}
142+
{focus === Focus.Miri && <SimplePane {...miri} kind="miri" />}
142143
{focus === Focus.Asm && <PaneWithCode {...assembly} kind="asm" />}
143144
{focus === Focus.LlvmIr && <PaneWithCode {...llvmIr} kind="llvm-ir" />}
144145
{focus === Focus.Mir && <PaneWithCode {...mir} kind="mir" />}
@@ -163,6 +164,10 @@ const Output: React.SFC<OutputProps> = ({
163164
label="Clippy"
164165
onClick={props.focusClippy}
165166
tabProps={clippy} />
167+
<Tab kind={Focus.Miri} focus={focus}
168+
label="Miri"
169+
onClick={props.focusMiri}
170+
tabProps={miri} />
166171
<Tab kind={Focus.Asm} focus={focus}
167172
label="ASM"
168173
onClick={props.focusAssembly}
@@ -208,6 +213,7 @@ interface OutputProps extends OutputState {
208213
focusExecute: () => void;
209214
focusFormat: () => void;
210215
focusClippy: () => void;
216+
focusMiri: () => void;
211217
focusAssembly: () => void;
212218
focusLlvmIr: () => void;
213219
focusMir: () => void;
@@ -225,6 +231,7 @@ const mapDispatchToProps = ({
225231
focusExecute: () => changeFocus(Focus.Execute),
226232
focusFormat: () => changeFocus(Focus.Format),
227233
focusClippy: () => changeFocus(Focus.Clippy),
234+
focusMiri: () => changeFocus(Focus.Miri),
228235
focusAssembly: () => changeFocus(Focus.Asm),
229236
focusLlvmIr: () => changeFocus(Focus.LlvmIr),
230237
focusMir: () => changeFocus(Focus.Mir),

ui/frontend/ToolsMenu.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import MenuGroup from './MenuGroup';
77
import {
88
performClippy,
99
performFormat,
10+
performMiri,
1011
} from './actions';
1112
import State from './state';
1213

1314
interface ToolsMenuProps {
1415
clippy: () => any;
16+
miri: () => any;
1517
format: () => any;
1618
close: () => void;
1719
}
@@ -28,11 +30,18 @@ const ToolsMenu: React.SFC<ToolsMenuProps> = props => (
2830
onClick={() => { props.clippy(); props.close(); }}>
2931
Catch common mistakes and improve the code using the Clippy linter.
3032
</ButtonMenuItem>
33+
<ButtonMenuItem
34+
name="Miri"
35+
onClick={() => { props.miri(); props.close(); }}>
36+
Execute this program in the Miri interpreter to detect certain
37+
cases of undefined behavior (like out-of-bounds memory access).
38+
</ButtonMenuItem>
3139
</MenuGroup>
3240
);
3341

3442
const mapDispatchToProps = ({
3543
clippy: performClippy,
44+
miri: performMiri,
3645
format: performFormat,
3746
});
3847

ui/frontend/actions.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const routes = {
2323
execute: { pathname: '/execute' },
2424
format: { pathname: '/format' },
2525
clippy: { pathname: '/clippy' },
26+
miri: { pathname: '/miri' },
2627
meta: {
2728
crates: { pathname: '/meta/crates' },
2829
version: {
@@ -78,6 +79,9 @@ export enum ActionType {
7879
RequestClippy = 'REQUEST_CLIPPY',
7980
ClippySucceeded = 'CLIPPY_SUCCEEDED',
8081
ClippyFailed = 'CLIPPY_FAILED',
82+
RequestMiri = 'REQUEST_MIRI',
83+
MiriSucceeded = 'MIRI_SUCCEEDED',
84+
MiriFailed = 'MIRI_FAILED',
8185
RequestGistLoad = 'REQUEST_GIST_LOAD',
8286
GistLoadSucceeded = 'GIST_LOAD_SUCCEEDED',
8387
GistLoadFailed = 'GIST_LOAD_FAILED',
@@ -387,6 +391,29 @@ export function performClippy(): ThunkAction {
387391
};
388392
}
389393

394+
const requestMiri = () =>
395+
createAction(ActionType.RequestMiri);
396+
397+
const receiveMiriSuccess = ({ stdout, stderr }) =>
398+
createAction(ActionType.MiriSucceeded, { stdout, stderr });
399+
400+
const receiveMiriFailure = ({ error }) =>
401+
createAction(ActionType.MiriFailed, { error });
402+
403+
export function performMiri(): ThunkAction {
404+
// TODO: Check a cache
405+
return function(dispatch, getState) {
406+
dispatch(requestMiri());
407+
408+
const { code } = getState();
409+
const body = { code };
410+
411+
return jsonPost(routes.miri, body)
412+
.then(json => dispatch(receiveMiriSuccess(json)))
413+
.catch(json => dispatch(receiveMiriFailure(json)));
414+
};
415+
}
416+
390417
interface GistSuccessProps {
391418
id: string;
392419
url: string;
@@ -610,6 +637,9 @@ export type Action =
610637
| ReturnType<typeof requestClippy>
611638
| ReturnType<typeof receiveClippySuccess>
612639
| ReturnType<typeof receiveClippyFailure>
640+
| ReturnType<typeof requestMiri>
641+
| ReturnType<typeof receiveMiriSuccess>
642+
| ReturnType<typeof receiveMiriFailure>
613643
| ReturnType<typeof requestGistLoad>
614644
| ReturnType<typeof receiveGistLoadSuccess>
615645
| ReturnType<typeof receiveGistLoadFailure>

0 commit comments

Comments
 (0)