safety(codegen): debug_assert companion for every unsafe get_unchecked
Pair every codegen-emitted `unsafe { ... get_unchecked ... }` with a
debug_assert! that re-states the safety obligation as a runtime
predicate. Free in release (debug_assert! compiles to nothing); in
dev/test/miri builds, every dispatch through the 68 behavior tests
exercises these checks, so any future regression in the codegen's
invariants trips loudly.
Sites covered:
- step()'s __DISPATCH lookup (sync + async)
- apply_path()'s __PATH_RANGE +
__PATH_DATA[..] slice ranges (sync + async)
- follow_defaults()'s __DEFAULT_CHILD lookup (sync + async)
- do_terminate()'s __TERMINATE_RANGE +
__TERMINATE_DATA[..] slice ranges (sync + async)
Verification:
- cargo nextest: 68/68 pass (debug_assert! active)
- cargo +nightly miri test: 66/66 pass (UB detector)
- cargo bench --bench dispatch: zero release-mode regression
(asserts compile out as expected)
This commit is contained in:
1 file changed
+13
@@ -2691,6 +2691,11 @@ pub fn generate(ir: &Ir) -> TokenStream {
|
|||||||
// values < #n_states. __ev_id was checked != u16::MAX
|
// values < #n_states. __ev_id was checked != u16::MAX
|
||||||
// and __event_to_id returns < #n_events otherwise.
|
// and __event_to_id returns < #n_events otherwise.
|
||||||
// Hence __dispatch_idx < #n_dispatch = __DISPATCH.len().
|
// Hence __dispatch_idx < #n_dispatch = __DISPATCH.len().
|
||||||
|
// The debug_assert is the safety obligation
|
||||||
|
// expressed at runtime in debug builds — every
|
||||||
|
// hsmc behavior test exercises this site, so a
|
||||||
|
// codegen invariant break trips it loudly.
|
||||||
|
debug_assert!(__dispatch_idx < __DISPATCH.len());
|
||||||
let (__handler_state, __actions, __target) = unsafe {
|
let (__handler_state, __actions, __target) = unsafe {
|
||||||
*__DISPATCH.get_unchecked(__dispatch_idx)
|
*__DISPATCH.get_unchecked(__dispatch_idx)
|
||||||
};
|
};
|
||||||
@@ -2973,7 +2978,9 @@ pub fn generate(ir: &Ir) -> TokenStream {
|
|||||||
// The triple (s, m, e) was emitted at codegen with the
|
// The triple (s, m, e) was emitted at codegen with the
|
||||||
// invariant 0 <= s <= m <= e <= __PATH_DATA.len(), so the
|
// invariant 0 <= s <= m <= e <= __PATH_DATA.len(), so the
|
||||||
// two slice ranges are in-bounds.
|
// two slice ranges are in-bounds.
|
||||||
|
debug_assert!(idx < __PATH_RANGE.len());
|
||||||
let (s, m, e) = unsafe { *__PATH_RANGE.get_unchecked(idx) };
|
let (s, m, e) = unsafe { *__PATH_RANGE.get_unchecked(idx) };
|
||||||
|
debug_assert!((s as usize) <= (m as usize) && (m as usize) <= (e as usize) && (e as usize) <= __PATH_DATA.len());
|
||||||
let exit_path = unsafe {
|
let exit_path = unsafe {
|
||||||
__PATH_DATA.get_unchecked(s as usize..m as usize)
|
__PATH_DATA.get_unchecked(s as usize..m as usize)
|
||||||
};
|
};
|
||||||
@@ -3107,6 +3114,7 @@ pub fn generate(ir: &Ir) -> TokenStream {
|
|||||||
while let Some(cur) = self.current {
|
while let Some(cur) = self.current {
|
||||||
// SAFETY: cur is a state id < #n_states (codegen
|
// SAFETY: cur is a state id < #n_states (codegen
|
||||||
// invariant — see __DISPATCH lookup safety note).
|
// invariant — see __DISPATCH lookup safety note).
|
||||||
|
debug_assert!((cur as usize) < __DEFAULT_CHILD.len());
|
||||||
let target = match unsafe { *__DEFAULT_CHILD.get_unchecked(cur as usize) } {
|
let target = match unsafe { *__DEFAULT_CHILD.get_unchecked(cur as usize) } {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => return,
|
None => return,
|
||||||
@@ -3128,6 +3136,7 @@ pub fn generate(ir: &Ir) -> TokenStream {
|
|||||||
async fn follow_defaults(&mut self) {
|
async fn follow_defaults(&mut self) {
|
||||||
while let Some(cur) = self.current {
|
while let Some(cur) = self.current {
|
||||||
// SAFETY: see sync branch — same invariant.
|
// SAFETY: see sync branch — same invariant.
|
||||||
|
debug_assert!((cur as usize) < __DEFAULT_CHILD.len());
|
||||||
let target = match unsafe { *__DEFAULT_CHILD.get_unchecked(cur as usize) } {
|
let target = match unsafe { *__DEFAULT_CHILD.get_unchecked(cur as usize) } {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => return,
|
None => return,
|
||||||
@@ -3208,7 +3217,9 @@ pub fn generate(ir: &Ir) -> TokenStream {
|
|||||||
if let Some(cur) = self.current {
|
if let Some(cur) = self.current {
|
||||||
// SAFETY: cur < #n_states (codegen invariant). The
|
// SAFETY: cur < #n_states (codegen invariant). The
|
||||||
// (s, e) tuple was emitted with 0 <= s <= e <= len.
|
// (s, e) tuple was emitted with 0 <= s <= e <= len.
|
||||||
|
debug_assert!((cur as usize) < __TERMINATE_RANGE.len());
|
||||||
let (s, e) = unsafe { *__TERMINATE_RANGE.get_unchecked(cur as usize) };
|
let (s, e) = unsafe { *__TERMINATE_RANGE.get_unchecked(cur as usize) };
|
||||||
|
debug_assert!((s as usize) <= (e as usize) && (e as usize) <= __TERMINATE_DATA.len());
|
||||||
let path = unsafe {
|
let path = unsafe {
|
||||||
__TERMINATE_DATA.get_unchecked(s as usize..e as usize)
|
__TERMINATE_DATA.get_unchecked(s as usize..e as usize)
|
||||||
};
|
};
|
||||||
@@ -3226,7 +3237,9 @@ pub fn generate(ir: &Ir) -> TokenStream {
|
|||||||
async fn do_terminate(&mut self) {
|
async fn do_terminate(&mut self) {
|
||||||
if let Some(cur) = self.current {
|
if let Some(cur) = self.current {
|
||||||
// SAFETY: see sync branch — same invariant.
|
// SAFETY: see sync branch — same invariant.
|
||||||
|
debug_assert!((cur as usize) < __TERMINATE_RANGE.len());
|
||||||
let (s, e) = unsafe { *__TERMINATE_RANGE.get_unchecked(cur as usize) };
|
let (s, e) = unsafe { *__TERMINATE_RANGE.get_unchecked(cur as usize) };
|
||||||
|
debug_assert!((s as usize) <= (e as usize) && (e as usize) <= __TERMINATE_DATA.len());
|
||||||
let path = unsafe {
|
let path = unsafe {
|
||||||
__TERMINATE_DATA.get_unchecked(s as usize..e as usize)
|
__TERMINATE_DATA.get_unchecked(s as usize..e as usize)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user