Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

misra-rust-poc

Small, runnable evidence for two questions in the MISRA C++:2023 → Rust mapping (issue #575, PR #1226).

Everything here compiles and runs. Reproduce it all with:

./run-evidence.sh
cargo test

Verified on rustc 1.96.0 (ac68faa20 2026-05-25), edition 2024, aarch64-apple-darwin.


Rule 10.4.1 — "The asm declaration shall not be used"

The mapping rationale currently says:

In safe rust the use of inline assembly is not possible, therefore this rule doesn't apply to safe rust.

Testing the three ways to write assembly in Rust gives a more nuanced picture:

Mechanism Needs an unsafe block? Rejected by #![forbid(unsafe_code)]?
asm! yes yes — "usage of an unsafe block"
global_asm! no yes — "using this macro is unsafe even though it does not need an unsafe block"
#[unsafe(naked)] + naked_asm! no no — compiles cleanly

Three conclusions:

  1. The quoted sentence is false as written. global_asm! compiles with no unsafe block anywhere (src/bin/a1_global_asm_no_unsafe_block.rs).

  2. The unsafe-only classification is still defensible — but the criterion should be forbid(unsafe_code), not impossibility. The compiler states outright that global_asm! is unsafe code that merely doesn't need the block syntax (negative/n1_global_asm_forbidden.rs). That is a precise, checkable criterion; "not possible" is not.

  3. global_asm! can run before main (src/bin/a3_ctor_runs_before_main.rs). It can emit a constructor entry, so the code executes with no call site anywhere in the Rust source and no unsafe keyword in the file. A reviewer auditing a safe-Rust crate has nothing in the source to look at.

  4. Naked functions pass #![forbid(unsafe_code)] entirely (src/bin/a2_naked_passes_forbid.rs). A whole function body of raw machine code compiles inside a crate that has declared itself free of unsafe code. Naked functions were stabilized in Rust 1.88, after the lint gained its global_asm! coverage, so this may simply be a gap in unsafe_code worth reporting upstream. Either way it means naked functions are assembly that no standard safety gate currently catches, which is relevant both to how 10.4.1 is worded and to the consortium's Clippy lints goal.

Rule 11.6.2 — "The value of an object must not be read before it has been set"

src/bin/b1_uninit_heap_read.rs reads heap storage that was never written:

let p = unsafe { alloc(layout) } as *mut u32;  // p IS initialized
let v = unsafe { *p };                         // never written -> UB

Two points for the rationale:

  • It separates 11.6.2 from 11.6.1. No variable is uninitialized here — p is a perfectly good pointer. What is unset is the object it addresses, which is exactly the noun 11.6.2 uses. (MaybeUninit is a poor example for this, because the ratified 11.6.1 rationale already names MaybeUninit as the reason 11.6.1 applies to unsafe Rust — both rules are engaged there, not just one.)
  • It printed 0. Fresh pages from the OS are usually zeroed, so this UB routinely looks like it works. Reading uninitialized memory is UB even for integer types — not "you get a garbage value" — which is the part most often misunderstood, and the reason MaybeUninit exists.

Run it without cloning (Rust Playground)

Verified on the official Playground, rustc 1.98.1 (2026-09-01), edition 2024, x86_64 Linux. The Mach-O demos in this repo use __DATA,__mod_init_func; these use the ELF equivalent .init_array.

  • Assembly runs before main, no unsafe anywhere in the file — prints [ctor] I ran first. then [main] main started. Run it
  • global_asm! is rejected by #![forbid(unsafe_code)] — "using this macro is unsafe even though it does not need an unsafe block" Run it
  • Naked functions are also rejected by #![forbid(unsafe_code)] on 1.98.1 (this was a2, now negative/n3) — "usage of the unsafe #[naked] attribute" Run it

Continuous re-run

.github/workflows/evidence.yml rebuilds and re-runs every demo on stable, on both Linux and macOS, on each push and once a month. Every claim here is a claim about what one toolchain accepts, so it is worth exactly as much as its last re-run. A separate toolchain-drift job asserts both halves of the correction above: that 1.96.0 accepts the naked-function file and that current stable rejects it. If either flips, CI fails rather than the README quietly going stale.

The constructor demo now covers Mach-O (__DATA,__mod_init_func, aarch64) and x86_64 ELF (.init_array); CI checks that [ctor] I ran first. really is the first line of output on both.

Correction: the naked-function lint gap was real, and is now closed

Earlier revisions of this repo claimed that #![forbid(unsafe_code)] rejects global_asm! but lets naked functions through, and suggested that was a lint gap worth reporting upstream. That was true on rustc 1.96.0 (2026-05-25), where src/bin/a2_naked_passes_forbid.rs still compiles and runs. It is no longer true on rustc 1.98.1, which rejects #[unsafe(naked)] under the same lint with a dedicated diagnostic. Nothing needs reporting upstream; it has been fixed.

This does not affect the Rule 10.4.1 argument, which rests on global_asm! requiring no unsafe block at all, not on the behaviour of the unsafe_code lint. It is also a small illustration of the point the argument is about: a toolchain two releases old gives a different answer about what is and is not reachable from safe Rust.

Why this matters for the classification

MISRA C++:2023 gives 10.4.1 category Required, and its rationale concerns the asm declaration being conditionally-supported, producing implementation-defined behaviour, and harming portability, with intrinsics named as the better modern alternative. None of it concerns memory safety.

Rust's unsafe keyword gates memory safety and nothing else. So "does it need unsafe" does not track what this rule protects, and the harm the rule describes is reachable from safe Rust via global_asm!.

(The standard's text is not quoted here. It is commercially licensed and not redistributable; the above is a characterisation, not a reproduction.)

What this does not establish

These are facts about rustc, not about MISRA. Whether they change a classification depends on MISRA C++:2023's rationale and amplification text for 10.4.1, which the author does not have access to — only the public MathWorks headline listing. Corrections welcome.

Layout

src/bin/a1_global_asm_no_unsafe_block.rs   global_asm! with no unsafe block   -> compiles
src/bin/a2_naked_passes_forbid.rs          naked fn under forbid(unsafe_code) -> compiles
src/bin/a3_ctor_runs_before_main.rs        global_asm! ctor runs before main  -> compiles
src/bin/b1_uninit_heap_read.rs             reads unwritten heap storage       -> UB, prints 0
negative/n1_global_asm_forbidden.rs        global_asm! under forbid           -> must fail
negative/n2_asm_forbidden.rs               unsafe block under forbid          -> must fail
tests/negative.rs                          asserts the two negatives fail
run-evidence.sh                            runs everything, prints the table above

About

Runnable evidence for MISRA C++:2023 to Rust mapping questions (rules 10.4.1 and 11.6.2)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages