Skip to content

Commit 0201f9d

Browse files
committed
Auto merge of #128504 - matthiaskrgr:rollup-pawylnk, r=matthiaskrgr
Rollup of 6 pull requests Successful merges: - #127490 (Add target page for riscv64gc-unknown-linux-gnu) - #128433 (fix(hermit): `deny(unsafe_op_in_unsafe_fn)`) - #128482 (interpret: on a signed deref check, mention the right pointer in the error) - #128496 (Fix removed `box_syntax` diagnostic if source isn't available) - #128497 (fix dropck documentation for `[T;0]` special-case) - #128499 (chore: refactor backtrace formatting) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 0ea782c + 4520d77 commit 0201f9d

26 files changed

+93
-101
lines changed

src/alloc_addresses/mod.rs

+20-18
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rand::Rng;
1111

1212
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1313
use rustc_span::Span;
14-
use rustc_target::abi::{Align, HasDataLayout, Size};
14+
use rustc_target::abi::{Align, Size};
1515

1616
use crate::{concurrency::VClock, *};
1717

@@ -105,15 +105,17 @@ impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
105105
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
106106
// Returns the exposed `AllocId` that corresponds to the specified addr,
107107
// or `None` if the addr is out of bounds
108-
fn alloc_id_from_addr(&self, addr: u64) -> Option<AllocId> {
108+
fn alloc_id_from_addr(&self, addr: u64, size: i64) -> Option<AllocId> {
109109
let ecx = self.eval_context_ref();
110110
let global_state = ecx.machine.alloc_addresses.borrow();
111111
assert!(global_state.provenance_mode != ProvenanceMode::Strict);
112112

113+
// We always search the allocation to the right of this address. So if the size is structly
114+
// negative, we have to search for `addr-1` instead.
115+
let addr = if size >= 0 { addr } else { addr.saturating_sub(1) };
113116
let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr);
114117

115118
// Determine the in-bounds provenance for this pointer.
116-
// (This is only called on an actual access, so in-bounds is the only possible kind of provenance.)
117119
let alloc_id = match pos {
118120
Ok(pos) => Some(global_state.int_to_ptr_map[pos].1),
119121
Err(0) => None,
@@ -305,20 +307,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
305307

306308
let (prov, offset) = ptr.into_parts(); // offset is relative (AllocId provenance)
307309
let alloc_id = prov.alloc_id();
308-
let base_addr = ecx.addr_from_alloc_id(alloc_id, kind)?;
309310

310-
// Add offset with the right kind of pointer-overflowing arithmetic.
311-
let dl = ecx.data_layout();
312-
let absolute_addr = dl.overflowing_offset(base_addr, offset.bytes()).0;
313-
Ok(interpret::Pointer::new(
311+
// Get a pointer to the beginning of this allocation.
312+
let base_addr = ecx.addr_from_alloc_id(alloc_id, kind)?;
313+
let base_ptr = interpret::Pointer::new(
314314
Provenance::Concrete { alloc_id, tag },
315-
Size::from_bytes(absolute_addr),
316-
))
315+
Size::from_bytes(base_addr),
316+
);
317+
// Add offset with the right kind of pointer-overflowing arithmetic.
318+
Ok(base_ptr.wrapping_offset(offset, ecx))
317319
}
318320

319321
/// When a pointer is used for a memory access, this computes where in which allocation the
320322
/// access is going.
321-
fn ptr_get_alloc(&self, ptr: interpret::Pointer<Provenance>) -> Option<(AllocId, Size)> {
323+
fn ptr_get_alloc(
324+
&self,
325+
ptr: interpret::Pointer<Provenance>,
326+
size: i64,
327+
) -> Option<(AllocId, Size)> {
322328
let ecx = self.eval_context_ref();
323329

324330
let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
@@ -327,20 +333,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
327333
alloc_id
328334
} else {
329335
// A wildcard pointer.
330-
ecx.alloc_id_from_addr(addr.bytes())?
336+
ecx.alloc_id_from_addr(addr.bytes(), size)?
331337
};
332338

333339
// This cannot fail: since we already have a pointer with that provenance, adjust_alloc_root_pointer
334340
// must have been called in the past, so we can just look up the address in the map.
335341
let base_addr = *ecx.machine.alloc_addresses.borrow().base_addr.get(&alloc_id).unwrap();
336342

337343
// Wrapping "addr - base_addr"
338-
#[allow(clippy::cast_possible_wrap)] // we want to wrap here
339-
let neg_base_addr = (base_addr as i64).wrapping_neg();
340-
Some((
341-
alloc_id,
342-
Size::from_bytes(ecx.overflowing_signed_offset(addr.bytes(), neg_base_addr).0),
343-
))
344+
let rel_offset = ecx.truncate_to_target_usize(addr.bytes().wrapping_sub(base_addr));
345+
Some((alloc_id, Size::from_bytes(rel_offset)))
344346
}
345347
}
346348

src/borrow_tracker/stacked_borrows/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
673673
// attempt to use it for a non-zero-sized access.
674674
// Dangling slices are a common case here; it's valid to get their length but with raw
675675
// pointer tagging for example all calls to get_unchecked on them are invalid.
676-
if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr()) {
676+
if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr(), 0) {
677677
log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
678678
// Still give it the new provenance, it got retagged after all.
679679
return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag }));
@@ -685,7 +685,7 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
685685
}
686686
}
687687

688-
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr())?;
688+
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr(), 0)?;
689689
log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
690690

691691
trace!(

src/borrow_tracker/tree_borrows/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
223223
};
224224

225225
trace!("Reborrow of size {:?}", ptr_size);
226-
let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr()) {
226+
let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr(), 0) {
227227
Ok(data) => {
228228
// Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
229229
// After all, the pointer may be lazily initialized outside this initial range.

src/concurrency/data_race.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
11801180
// We avoid `get_ptr_alloc` since we do *not* want to run the access hooks -- the actual
11811181
// access will happen later.
11821182
let (alloc_id, _offset, _prov) = this
1183-
.ptr_try_get_alloc_id(place.ptr())
1183+
.ptr_try_get_alloc_id(place.ptr(), 0)
11841184
.expect("there are no zero-sized atomic accesses");
11851185
if this.get_alloc_mutability(alloc_id)? == Mutability::Not {
11861186
// See if this is fine.
@@ -1307,7 +1307,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
13071307
if let Some(data_race) = &this.machine.data_race {
13081308
if data_race.race_detecting() {
13091309
let size = place.layout.size;
1310-
let (alloc_id, base_offset, _prov) = this.ptr_get_alloc_id(place.ptr())?;
1310+
let (alloc_id, base_offset, _prov) = this.ptr_get_alloc_id(place.ptr(), 0)?;
13111311
// Load and log the atomic operation.
13121312
// Note that atomic loads are possible even from read-only allocations, so `get_alloc_extra_mut` is not an option.
13131313
let alloc_meta = this.get_alloc_extra(alloc_id)?.data_race.as_ref().unwrap();

src/concurrency/weak_memory.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
468468
init: Scalar,
469469
) -> InterpResult<'tcx> {
470470
let this = self.eval_context_mut();
471-
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr())?;
471+
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr(), 0)?;
472472
if let (
473473
crate::AllocExtra { weak_memory: Some(alloc_buffers), .. },
474474
crate::MiriMachine { data_race: Some(global), threads, .. },
@@ -495,7 +495,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
495495
) -> InterpResult<'tcx, Scalar> {
496496
let this = self.eval_context_ref();
497497
if let Some(global) = &this.machine.data_race {
498-
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr())?;
498+
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr(), 0)?;
499499
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
500500
if atomic == AtomicReadOrd::SeqCst {
501501
global.sc_read(&this.machine.threads);
@@ -535,7 +535,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
535535
init: Scalar,
536536
) -> InterpResult<'tcx> {
537537
let this = self.eval_context_mut();
538-
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr())?;
538+
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr(), 0)?;
539539
if let (
540540
crate::AllocExtra { weak_memory: Some(alloc_buffers), .. },
541541
crate::MiriMachine { data_race: Some(global), threads, .. },
@@ -585,7 +585,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
585585
global.sc_read(&this.machine.threads);
586586
}
587587
let size = place.layout.size;
588-
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr())?;
588+
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr(), 0)?;
589589
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
590590
let buffer = alloc_buffers
591591
.get_or_create_store_buffer(alloc_range(base_offset, size), init)?;

src/helpers.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
606606
}
607607
// The part between the end_ptr and the end of the place is also frozen.
608608
// So pretend there is a 0-sized `UnsafeCell` at the end.
609-
unsafe_cell_action(&place.ptr().offset(size, this)?, Size::ZERO)?;
609+
unsafe_cell_action(&place.ptr().wrapping_offset(size, this), Size::ZERO)?;
610610
// Done!
611611
return Ok(());
612612

@@ -975,7 +975,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
975975
loop {
976976
// FIXME: We are re-getting the allocation each time around the loop.
977977
// Would be nice if we could somehow "extend" an existing AllocRange.
978-
let alloc = this.get_ptr_alloc(ptr.offset(len, this)?, size1)?.unwrap(); // not a ZST, so we will get a result
978+
let alloc = this.get_ptr_alloc(ptr.wrapping_offset(len, this), size1)?.unwrap(); // not a ZST, so we will get a result
979979
let byte = alloc.read_integer(alloc_range(Size::ZERO, size1))?.to_u8()?;
980980
if byte == 0 {
981981
break;
@@ -1039,7 +1039,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
10391039
break;
10401040
} else {
10411041
wchars.push(wchar_int.try_into().unwrap());
1042-
ptr = ptr.offset(size, this)?;
1042+
ptr = ptr.wrapping_offset(size, this);
10431043
}
10441044
}
10451045

src/machine.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -1198,19 +1198,23 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
11981198
}
11991199
}
12001200

1201-
/// Convert a pointer with provenance into an allocation-offset pair,
1202-
/// or a `None` with an absolute address if that conversion is not possible.
1201+
/// Convert a pointer with provenance into an allocation-offset pair and extra provenance info.
1202+
/// `size` says how many bytes of memory are expected at that pointer. The *sign* of `size` can
1203+
/// be used to disambiguate situations where a wildcard pointer sits right in between two
1204+
/// allocations.
12031205
///
1204-
/// This is called when a pointer is about to be used for memory access,
1205-
/// an in-bounds check, or anything else that requires knowing which allocation it points to.
1206+
/// If `ptr.provenance.get_alloc_id()` is `Some(p)`, the returned `AllocId` must be `p`.
12061207
/// The resulting `AllocId` will just be used for that one step and the forgotten again
12071208
/// (i.e., we'll never turn the data returned here back into a `Pointer` that might be
12081209
/// stored in machine state).
1210+
///
1211+
/// When this fails, that means the pointer does not point to a live allocation.
12091212
fn ptr_get_alloc(
12101213
ecx: &MiriInterpCx<'tcx>,
12111214
ptr: StrictPointer,
1215+
size: i64,
12121216
) -> Option<(AllocId, Size, Self::ProvenanceExtra)> {
1213-
let rel = ecx.ptr_get_alloc(ptr);
1217+
let rel = ecx.ptr_get_alloc(ptr, size);
12141218

12151219
rel.map(|(alloc_id, size)| {
12161220
let tag = match ptr.provenance {

src/shims/backtrace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
116116

117117
let ptr = this.read_pointer(ptr)?;
118118
// Take apart the pointer, we need its pieces. The offset encodes the span.
119-
let (alloc_id, offset, _prov) = this.ptr_get_alloc_id(ptr)?;
119+
let (alloc_id, offset, _prov) = this.ptr_get_alloc_id(ptr, 0)?;
120120

121121
// This has to be an actual global fn ptr, not a dlsym function.
122122
let fn_instance = if let Some(GlobalAlloc::Function { instance, .. }) =

src/shims/foreign_items.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
278278
"miri_get_alloc_id" => {
279279
let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
280280
let ptr = this.read_pointer(ptr)?;
281-
let (alloc_id, _, _) = this.ptr_get_alloc_id(ptr).map_err(|_e| {
281+
let (alloc_id, _, _) = this.ptr_get_alloc_id(ptr, 0).map_err(|_e| {
282282
err_machine_stop!(TerminationInfo::Abort(format!(
283283
"pointer passed to `miri_get_alloc_id` must not be dangling, got {ptr:?}"
284284
)))
@@ -311,7 +311,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
311311
"miri_static_root" => {
312312
let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
313313
let ptr = this.read_pointer(ptr)?;
314-
let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr)?;
314+
let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr, 0)?;
315315
if offset != Size::ZERO {
316316
throw_unsup_format!(
317317
"pointer passed to `miri_static_root` must point to beginning of an allocated block"
@@ -392,7 +392,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
392392
"`miri_promise_symbolic_alignment`: pointer is not actually aligned"
393393
);
394394
}
395-
if let Ok((alloc_id, offset, ..)) = this.ptr_try_get_alloc_id(ptr) {
395+
if let Ok((alloc_id, offset, ..)) = this.ptr_try_get_alloc_id(ptr, 0) {
396396
let (_size, alloc_align, _kind) = this.get_alloc_info(alloc_id);
397397
// If the newly promised alignment is bigger than the native alignment of this
398398
// allocation, and bigger than the previously promised alignment, then set it.
@@ -584,8 +584,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
584584
let n = Size::from_bytes(this.read_target_usize(n)?);
585585

586586
// C requires that this must always be a valid pointer (C18 §7.1.4).
587-
this.ptr_get_alloc_id(left)?;
588-
this.ptr_get_alloc_id(right)?;
587+
this.ptr_get_alloc_id(left, 0)?;
588+
this.ptr_get_alloc_id(right, 0)?;
589589

590590
let result = {
591591
let left_bytes = this.read_bytes_ptr_strip_provenance(left, n)?;
@@ -612,7 +612,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
612612
let val = val as u8;
613613

614614
// C requires that this must always be a valid pointer (C18 §7.1.4).
615-
this.ptr_get_alloc_id(ptr)?;
615+
this.ptr_get_alloc_id(ptr, 0)?;
616616

617617
if let Some(idx) = this
618618
.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))?
@@ -622,7 +622,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
622622
{
623623
let idx = u64::try_from(idx).unwrap();
624624
#[allow(clippy::arithmetic_side_effects)] // idx < num, so this never wraps
625-
let new_ptr = ptr.offset(Size::from_bytes(num - idx - 1), this)?;
625+
let new_ptr = ptr.wrapping_offset(Size::from_bytes(num - idx - 1), this);
626626
this.write_pointer(new_ptr, dest)?;
627627
} else {
628628
this.write_null(dest)?;
@@ -639,14 +639,14 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
639639
let val = val as u8;
640640

641641
// C requires that this must always be a valid pointer (C18 §7.1.4).
642-
this.ptr_get_alloc_id(ptr)?;
642+
this.ptr_get_alloc_id(ptr, 0)?;
643643

644644
let idx = this
645645
.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))?
646646
.iter()
647647
.position(|&c| c == val);
648648
if let Some(idx) = idx {
649-
let new_ptr = ptr.offset(Size::from_bytes(idx as u64), this)?;
649+
let new_ptr = ptr.wrapping_offset(Size::from_bytes(idx as u64), this);
650650
this.write_pointer(new_ptr, dest)?;
651651
} else {
652652
this.write_null(dest)?;
@@ -681,8 +681,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
681681

682682
// C requires that this must always be a valid pointer, even if `n` is zero, so we better check that.
683683
// (This is more than Rust requires, so `mem_copy` is not sufficient.)
684-
this.ptr_get_alloc_id(ptr_dest)?;
685-
this.ptr_get_alloc_id(ptr_src)?;
684+
this.ptr_get_alloc_id(ptr_dest, 0)?;
685+
this.ptr_get_alloc_id(ptr_src, 0)?;
686686

687687
this.mem_copy(ptr_src, ptr_dest, Size::from_bytes(n), true)?;
688688
this.write_pointer(ptr_dest, dest)?;

src/shims/unix/env.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ impl<'tcx> UnixEnvVars<'tcx> {
8282
};
8383
// The offset is used to strip the "{name}=" part of the string.
8484
let var_ptr = var_ptr
85-
.offset(Size::from_bytes(u64::try_from(name.len()).unwrap().strict_add(1)), ecx)?;
85+
.wrapping_offset(Size::from_bytes(u64::try_from(name.len()).unwrap().strict_add(1)), ecx);
8686
Ok(Some(var_ptr))
8787
}
8888

src/shims/unix/fs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
996996
&this.ptr_to_mplace(entry, dirent64_layout),
997997
)?;
998998

999-
let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
999+
let name_ptr = entry.wrapping_offset(Size::from_bytes(d_name_offset), this);
10001000
this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?;
10011001

10021002
Some(entry)

src/shims/unix/linux/mem.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
5353
// We just allocated this, the access is definitely in-bounds and fits into our address space.
5454
// mmap guarantees new mappings are zero-init.
5555
this.write_bytes_ptr(
56-
ptr.offset(Size::from_bytes(old_size), this).unwrap().into(),
56+
ptr.wrapping_offset(Size::from_bytes(old_size), this).into(),
5757
std::iter::repeat(0u8).take(usize::try_from(increase).unwrap()),
5858
)
5959
.unwrap();

tests/fail-dep/libc/affinity.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: memory access failed: expected a pointer to 129 bytes of memory, but got ALLOC and there are only 128 bytes starting at that pointer
1+
error: Undefined Behavior: memory access failed: expected a pointer to 129 bytes of memory, but got ALLOC which is only 128 bytes from the end of the allocation
22
--> $DIR/affinity.rs:LL:CC
33
|
44
LL | let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>() + 1, &cpuset) };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 129 bytes of memory, but got ALLOC and there are only 128 bytes starting at that pointer
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 129 bytes of memory, but got ALLOC which is only 128 bytes from the end of the allocation
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

0 commit comments

Comments
 (0)