Auto merge of #3766 - RalfJung:tree-borrows-int2ptr, r=RalfJung

better diagnostics for Tree Borrows + int2ptr casts

- Entirely reject `-Zmiri-permissive-provenance -Zmiri-tree-borrows` since that combination just doesn't work
- In the int2ptr cast warning, when Tree Borrows is enabled, do not recommend `-Zmiri-permissive-provenance`, instead note that Tree Borrows does not support int2ptr casts

Fixes https://github.com/rust-lang/miri/issues/3764
This commit is contained in:
bors 2024-07-26 12:52:57 +00:00
commit bf4d4c035d
16 changed files with 201 additions and 138 deletions

View File

@ -620,6 +620,14 @@ fn main() {
"-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used"
);
}
// Tree Borrows + permissive provenance does not work.
if miri_config.provenance_mode == ProvenanceMode::Permissive
&& matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows))
{
show_error!(
"Tree Borrows does not support integer-to-pointer casts, and is hence not compatible with permissive provenance"
);
}
debug!("rustc arguments: {:?}", rustc_args);
debug!("crate arguments: {:?}", miri_config.args);

View File

@ -232,6 +232,10 @@ impl GlobalStateInner {
pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_>) {
self.root_ptr_tags.retain(|id, _| allocs.is_live(*id));
}
pub fn borrow_tracker_method(&self) -> BorrowTrackerMethod {
self.borrow_tracker_method
}
}
/// Which borrow tracking method to use

View File

@ -140,6 +140,15 @@ pub enum DiagLevel {
Note,
}
/// Generate a note/help text without a span.
macro_rules! note {
($($tt:tt)*) => { (None, format!($($tt)*)) };
}
/// Generate a note/help text with a span.
macro_rules! note_span {
($span:expr, $($tt:tt)*) => { (Some($span), format!($($tt)*)) };
}
/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
@ -228,38 +237,38 @@ pub fn report_error<'tcx>(
let helps = match info {
UnsupportedInIsolation(_) =>
vec![
(None, format!("set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;")),
(None, format!("or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning")),
note!("set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;"),
note!("or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning"),
],
UnsupportedForeignItem(_) => {
vec![
(None, format!("if this is a basic API commonly used on this target, please report an issue with Miri")),
(None, format!("however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases")),
note!("if this is a basic API commonly used on this target, please report an issue with Miri"),
note!("however, note that Miri does not aim to support every FFI function out there; for instance, we will not support APIs for things such as GUIs, scripting languages, or databases"),
]
}
StackedBorrowsUb { help, history, .. } => {
msg.extend(help.clone());
let mut helps = vec![
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental")),
(None, format!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information")),
note!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental"),
note!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information"),
];
if let Some(TagHistory {created, invalidated, protected}) = history.clone() {
helps.push((Some(created.1), created.0));
if let Some((msg, span)) = invalidated {
helps.push((Some(span), msg));
helps.push(note_span!(span, "{msg}"));
}
if let Some((protector_msg, protector_span)) = protected {
helps.push((Some(protector_span), protector_msg));
helps.push(note_span!(protector_span, "{protector_msg}"));
}
}
helps
},
TreeBorrowsUb { title: _, details, history } => {
let mut helps = vec![
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental"))
note!("this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental")
];
for m in details {
helps.push((None, m.clone()));
helps.push(note!("{m}"));
}
for event in history.events.clone() {
helps.push(event);
@ -268,26 +277,26 @@ pub fn report_error<'tcx>(
}
MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
vec![
(Some(*first), format!("it's first defined here, in crate `{first_crate}`")),
(Some(*second), format!("then it's defined here again, in crate `{second_crate}`")),
note_span!(*first, "it's first defined here, in crate `{first_crate}`"),
note_span!(*second, "then it's defined here again, in crate `{second_crate}`"),
],
SymbolShimClashing { link_name, span } =>
vec![(Some(*span), format!("the `{link_name}` symbol is defined here"))],
vec![note_span!(*span, "the `{link_name}` symbol is defined here")],
Int2PtrWithStrictProvenance =>
vec![(None, format!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"))],
vec![note!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead")],
DataRace { op1, extra, retag_explain, .. } => {
let mut helps = vec![(Some(op1.span), format!("and (1) occurred earlier here"))];
let mut helps = vec![note_span!(op1.span, "and (1) occurred earlier here")];
if let Some(extra) = extra {
helps.push((None, format!("{extra}")));
helps.push((None, format!("see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model")));
helps.push(note!("{extra}"));
helps.push(note!("see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model"));
}
if *retag_explain {
helps.push((None, "retags occur on all (re)borrows and as well as when references are copied or moved".to_owned()));
helps.push((None, "retags permit optimizations that insert speculative reads or writes".to_owned()));
helps.push((None, "therefore from the perspective of data races, a retag has the same implications as a read or write".to_owned()));
helps.push(note!("retags occur on all (re)borrows and as well as when references are copied or moved"));
helps.push(note!("retags permit optimizations that insert speculative reads or writes"));
helps.push(note!("therefore from the perspective of data races, a retag has the same implications as a read or write"));
}
helps.push((None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")));
helps.push((None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")));
helps.push(note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"));
helps.push(note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"));
helps
}
,
@ -332,32 +341,32 @@ pub fn report_error<'tcx>(
let helps = match e.kind() {
Unsupported(_) =>
vec![
(None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support")),
note!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support"),
],
UndefinedBehavior(AlignmentCheckFailed { .. })
if ecx.machine.check_alignment == AlignmentCheck::Symbolic
=>
vec![
(None, format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior")),
(None, format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives")),
note!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior"),
note!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives"),
],
UndefinedBehavior(info) => {
let mut helps = vec![
(None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")),
(None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")),
note!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior"),
note!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information"),
];
match info {
PointerUseAfterFree(alloc_id, _) | PointerOutOfBounds { alloc_id, .. } => {
if let Some(span) = ecx.machine.allocated_span(*alloc_id) {
helps.push((Some(span), format!("{:?} was allocated here:", alloc_id)));
helps.push(note_span!(span, "{:?} was allocated here:", alloc_id));
}
if let Some(span) = ecx.machine.deallocated_span(*alloc_id) {
helps.push((Some(span), format!("{:?} was deallocated here:", alloc_id)));
helps.push(note_span!(span, "{:?} was deallocated here:", alloc_id));
}
}
AbiMismatchArgument { .. } | AbiMismatchReturn { .. } => {
helps.push((None, format!("this means these two types are not *guaranteed* to be ABI-compatible across all targets")));
helps.push((None, format!("if you think this code should be accepted anyway, please report an issue with Miri")));
helps.push(note!("this means these two types are not *guaranteed* to be ABI-compatible across all targets"));
helps.push(note!("if you think this code should be accepted anyway, please report an issue with Miri"));
}
_ => {},
}
@ -639,60 +648,47 @@ impl<'tcx> MiriMachine<'tcx> {
let notes = match &e {
ProgressReport { block_count } => {
// It is important that each progress report is slightly different, since
// identical diagnostics are being deduplicated.
vec![(None, format!("so far, {block_count} basic blocks have been executed"))]
vec![note!("so far, {block_count} basic blocks have been executed")]
}
_ => vec![],
};
let helps = match &e {
Int2Ptr { details: true } =>
vec![
(
None,
format!(
"this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program"
),
Int2Ptr { details: true } => {
let mut v = vec![
note!(
"this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program"
),
(
None,
format!(
"see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation"
),
note!(
"see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation"
),
(
None,
format!(
"to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"
),
note!(
"to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"
),
(
None,
format!(
"you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics"
),
note!(
"you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics"
),
(
None,
format!(
"alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning"
),
),
],
];
if self.borrow_tracker.as_ref().is_some_and(|b| {
matches!(b.borrow().borrow_tracker_method(), BorrowTrackerMethod::TreeBorrows)
}) {
v.push(
note!("Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used")
);
} else {
v.push(
note!("alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning")
);
}
v
}
ExternTypeReborrow => {
vec![
(
None,
format!(
"`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"
),
note!(
"`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"
),
(
None,
format!(
"try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead"
),
note!(
"try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead"
),
]
}

View File

@ -1,5 +1,3 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@compile-flags: -Zmiri-permissive-provenance
fn ensure_allocs_can_be_adjacent() {

View File

@ -1,5 +1,4 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
//@compile-flags: -Zmiri-permissive-provenance
#![feature(ptr_internals)]
fn main() {

View File

@ -1,33 +0,0 @@
warning: integer-to-pointer cast
--> $DIR/box.rs:LL:CC
|
LL | let r2 = ((r as usize) + 0) as *mut i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
= help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning
= note: BACKTRACE:
= note: inside `into_raw` at $DIR/box.rs:LL:CC
note: inside `main`
--> $DIR/box.rs:LL:CC
|
LL | into_raw();
| ^^^^^^^^^^
warning: integer-to-pointer cast
--> $DIR/box.rs:LL:CC
|
LL | let r = ((u.as_ptr() as usize) + 0) as *mut i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= note: BACKTRACE:
= note: inside `into_unique` at $DIR/box.rs:LL:CC
note: inside `main`
--> $DIR/box.rs:LL:CC
|
LL | into_unique();
| ^^^^^^^^^^^^^

View File

@ -1,3 +0,0 @@
pair_foo = PairFoo { fst: Foo(42), snd: Foo(1337) }
foo #0 = Foo(42)
foo #1 = Foo(1337)

View File

@ -1,12 +1,14 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
#![feature(extern_types)]
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(extern_types, strict_provenance)]
use std::ptr;
extern "C" {
type Foo;
}
fn main() {
let x: &Foo = unsafe { &*(16 as *const Foo) };
let x: &Foo = unsafe { &*(ptr::without_provenance::<()>(16) as *const Foo) };
let _y: &Foo = &*x;
}

View File

@ -1,22 +1,8 @@
warning: integer-to-pointer cast
--> $DIR/extern_types.rs:LL:CC
|
LL | let x: &Foo = unsafe { &*(16 as *const Foo) };
| ^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
= help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning
= note: BACKTRACE:
= note: inside `main` at $DIR/extern_types.rs:LL:CC
warning: reborrow of reference to `extern type`
--> $DIR/extern_types.rs:LL:CC
|
LL | let x: &Foo = unsafe { &*(16 as *const Foo) };
| ^^^^^^^^^^^^^^^^^^^^ reborrow of a reference to `extern type` is not properly supported
LL | let x: &Foo = unsafe { &*(ptr::without_provenance::<()>(16) as *const Foo) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reborrow of a reference to `extern type` is not properly supported
|
= help: `extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code
= help: try running with `MIRIFLAGS=-Zmiri-tree-borrows` to use the more permissive but also even more experimental Tree Borrows aliasing checks instead

View File

@ -1,5 +1,3 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@compile-flags: -Zmiri-permissive-provenance
use std::mem;

View File

@ -1,5 +1,3 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
//@compile-flags: -Zmiri-permissive-provenance
#![feature(ptr_metadata, const_raw_ptr_comparison)]
#![allow(ambiguous_wide_pointer_comparisons)]

View File

@ -1,6 +1,7 @@
//@revisions: stack tree
// Tree Borrows doesn't support int2ptr casts, but let's make sure we don't immediately crash either.
//@[tree]compile-flags: -Zmiri-tree-borrows
//@compile-flags: -Zmiri-permissive-provenance
//@[stack]compile-flags: -Zmiri-permissive-provenance
use std::mem;
use std::ptr;

View File

@ -0,0 +1,89 @@
warning: integer-to-pointer cast
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | assert_eq!(1 as *const i32 as usize, 1);
| ^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
= help: Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used
= note: BACKTRACE:
= note: inside `ptr_int_casts` at $DIR/ptr_int_casts.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | ptr_int_casts();
| ^^^^^^^^^^^^^^^
warning: integer-to-pointer cast
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | assert_eq!((1 as *const i32).wrapping_offset(4) as usize, 1 + 4 * 4);
| ^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= note: BACKTRACE:
= note: inside `ptr_int_casts` at $DIR/ptr_int_casts.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | ptr_int_casts();
| ^^^^^^^^^^^^^^^
warning: integer-to-pointer cast
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | *val = (1 as *const u8).wrapping_offset(-4);
| ^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= note: BACKTRACE:
= note: inside `ptr_int_casts` at $DIR/ptr_int_casts.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | ptr_int_casts();
| ^^^^^^^^^^^^^^^
warning: integer-to-pointer cast
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | let y = y as *const _;
| ^^^^^^^^^^^^^ integer-to-pointer cast
|
= note: BACKTRACE:
= note: inside `ptr_int_casts` at $DIR/ptr_int_casts.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | ptr_int_casts();
| ^^^^^^^^^^^^^^^
warning: integer-to-pointer cast
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | let x: fn() -> i32 = unsafe { mem::transmute(y as *mut u8) };
| ^^^^^^^^^^^^ integer-to-pointer cast
|
= note: BACKTRACE:
= note: inside `ptr_int_casts` at $DIR/ptr_int_casts.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | ptr_int_casts();
| ^^^^^^^^^^^^^^^
warning: integer-to-pointer cast
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | assert_eq!((-1i32) as usize as *const i32 as usize, (-1i32) as usize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= note: BACKTRACE:
= note: inside `ptr_int_casts` at $DIR/ptr_int_casts.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_casts.rs:LL:CC
|
LL | ptr_int_casts();
| ^^^^^^^^^^^^^^^

View File

@ -1,6 +1,7 @@
//@revisions: stack tree
// Tree Borrows doesn't support int2ptr casts, but let's make sure we don't immediately crash either.
//@[tree]compile-flags: -Zmiri-tree-borrows
//@compile-flags: -Zmiri-permissive-provenance
//@[stack]compile-flags: -Zmiri-permissive-provenance
#![feature(strict_provenance, exposed_provenance)]
use std::ptr;

View File

@ -0,0 +1,19 @@
warning: integer-to-pointer cast
--> $DIR/ptr_int_from_exposed.rs:LL:CC
|
LL | let ptr = ptr::with_exposed_provenance::<i32>(x_usize).wrapping_offset(-128);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
= help: Tree Borrows does not support integer-to-pointer casts, so the program is likely to go wrong when this pointer gets used
= note: BACKTRACE:
= note: inside `ptr_roundtrip_out_of_bounds` at $DIR/ptr_int_from_exposed.rs:LL:CC
note: inside `main`
--> $DIR/ptr_int_from_exposed.rs:LL:CC
|
LL | ptr_roundtrip_out_of_bounds();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^