Skip to content

Commit 0d6923d

Browse files
Check vtable projections for validity in miri
1 parent 6c6d210 commit 0d6923d

File tree

20 files changed

+129
-76
lines changed

20 files changed

+129
-76
lines changed

compiler/rustc_codegen_llvm/src/common.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,10 @@ impl<'ll, 'tcx> ConstCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> {
290290
self.get_fn_addr(instance.polymorphize(self.tcx)),
291291
self.data_layout().instruction_address_space,
292292
),
293-
GlobalAlloc::VTable(ty, trait_ref) => {
293+
GlobalAlloc::VTable(ty, vtable) => {
294294
let alloc = self
295295
.tcx
296-
.global_alloc(self.tcx.vtable_allocation((ty, trait_ref)))
296+
.global_alloc(self.tcx.vtable_allocation((ty, vtable.principal())))
297297
.unwrap_memory();
298298
let init = const_alloc_to_llvm(self, alloc, /*static*/ false);
299299
let value = self.static_addr_of(init, alloc.inner().align, None);

compiler/rustc_const_eval/messages.ftl

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ const_eval_invalid_vtable_pointer =
198198
using {$pointer} as vtable pointer but it does not point to a vtable
199199
200200
const_eval_invalid_vtable_trait =
201-
using vtable for trait `{$vtable_trait}` but trait `{$expected_trait}` was expected
201+
using vtable for `{$allocated_vtable}` but `{$expected_vtable}` was expected
202202
203203
const_eval_lazy_lock =
204204
consider wrapping this expression in `std::sync::LazyLock::new(|| ...)`
@@ -459,7 +459,7 @@ const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, bu
459459
const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object
460460
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
461461
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
462-
const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$ref_trait}`, but encountered `{$vtable_trait}`
462+
const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_vtable}`, but encountered `{$allocated_vtable}`
463463
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
464464
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
465465
const_eval_validation_null_box = {$front_matter}: encountered a null box

compiler/rustc_const_eval/src/errors.rs

+6-12
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
525525
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
526526
diag.arg("pointer", ptr);
527527
}
528-
InvalidVTableTrait { expected_trait, vtable_trait } => {
529-
diag.arg("expected_trait", expected_trait.to_string());
530-
diag.arg(
531-
"vtable_trait",
532-
vtable_trait.map(|t| t.to_string()).unwrap_or_else(|| format!("<trivial>")),
533-
);
528+
InvalidVTableTrait { expected_vtable, allocated_vtable } => {
529+
diag.arg("expected_vtable", expected_vtable.to_string());
530+
diag.arg("allocated_vtable", allocated_vtable.to_string());
534531
}
535532
PointerUseAfterFree(alloc_id, msg) => {
536533
diag.arg("alloc_id", alloc_id)
@@ -780,12 +777,9 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
780777
DanglingPtrNoProvenance { pointer, .. } => {
781778
err.arg("pointer", pointer);
782779
}
783-
InvalidMetaWrongTrait { expected_trait: ref_trait, vtable_trait } => {
784-
err.arg("ref_trait", ref_trait.to_string());
785-
err.arg(
786-
"vtable_trait",
787-
vtable_trait.map(|t| t.to_string()).unwrap_or_else(|| format!("<trivial>")),
788-
);
780+
InvalidMetaWrongTrait { allocated_vtable, expected_vtable } => {
781+
err.arg("allocated_vtable", allocated_vtable.to_string());
782+
err.arg("expected_vtable", expected_vtable.to_string());
789783
}
790784
NullPtr { .. }
791785
| ConstRefToMutable

compiler/rustc_const_eval/src/interpret/cast.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
128128
CastKind::DynStar => {
129129
if let ty::Dynamic(data, _, ty::DynStar) = cast_ty.kind() {
130130
// Initial cast from sized to dyn trait
131-
let vtable = self.get_vtable_ptr(src.layout.ty, data.principal())?;
131+
let vtable = self.get_vtable_ptr(src.layout.ty, data)?;
132132
let vtable = Scalar::from_maybe_pointer(vtable, self);
133133
let data = self.read_immediate(src)?.to_scalar();
134134
let _assert_pointer_like = data.to_pointer(self)?;
@@ -446,12 +446,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
446446
}
447447

448448
// Get the destination trait vtable and return that.
449-
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
449+
let new_vptr = self.get_vtable_ptr(ty, data_b)?;
450450
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
451451
}
452452
(_, &ty::Dynamic(data, _, ty::Dyn)) => {
453453
// Initial cast from sized to dyn trait
454-
let vtable = self.get_vtable_ptr(src_pointee_ty, data.principal())?;
454+
let vtable = self.get_vtable_ptr(src_pointee_ty, data)?;
455455
let ptr = self.read_pointer(src)?;
456456
let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
457457
self.write_immediate(val, dest)

compiler/rustc_const_eval/src/interpret/memory.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -943,12 +943,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
943943
if offset.bytes() != 0 {
944944
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
945945
}
946-
let Some(GlobalAlloc::VTable(ty, vtable_trait)) = self.tcx.try_get_global_alloc(alloc_id)
946+
let Some(GlobalAlloc::VTable(ty, allocated_vtable)) =
947+
self.tcx.try_get_global_alloc(alloc_id)
947948
else {
948949
throw_ub!(InvalidVTablePointer(Pointer::new(alloc_id, offset)))
949950
};
950-
if let Some(expected_trait) = expected_trait {
951-
self.check_vtable_for_type(vtable_trait, expected_trait)?;
951+
if let Some(expected_vtable) = expected_trait {
952+
self.check_vtable_for_type(allocated_vtable, expected_vtable)?;
952953
}
953954
Ok(ty)
954955
}
@@ -1113,11 +1114,8 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for DumpAllocs<'a, 'tcx, M> {
11131114
Some(GlobalAlloc::Function { instance, .. }) => {
11141115
write!(fmt, " (fn: {instance})")?;
11151116
}
1116-
Some(GlobalAlloc::VTable(ty, Some(trait_ref))) => {
1117-
write!(fmt, " (vtable: impl {trait_ref} for {ty})")?;
1118-
}
1119-
Some(GlobalAlloc::VTable(ty, None)) => {
1120-
write!(fmt, " (vtable: impl <auto trait> for {ty})")?;
1117+
Some(GlobalAlloc::VTable(ty, vtable)) => {
1118+
write!(fmt, " (vtable: impl {vtable} for {ty})")?;
11211119
}
11221120
Some(GlobalAlloc::Static(did)) => {
11231121
write!(fmt, " (static: {})", self.ecx.tcx.def_path_str(did))?;

compiler/rustc_const_eval/src/interpret/traits.rs

+40-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rustc_middle::mir::interpret::{InterpResult, Pointer};
22
use rustc_middle::ty::layout::LayoutOf;
3-
use rustc_middle::ty::{self, Ty, TyCtxt, VtblEntry};
3+
use rustc_middle::ty::{self, ExistentialPredicateStableCmpExt, Ty, TyCtxt, VtblEntry};
44
use rustc_target::abi::{Align, Size};
55
use tracing::trace;
66

@@ -18,19 +18,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
1818
pub fn get_vtable_ptr(
1919
&self,
2020
ty: Ty<'tcx>,
21-
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
21+
vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
2222
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
23-
trace!("get_vtable(trait_ref={:?})", poly_trait_ref);
23+
trace!("get_vtable(ty={ty:?}, vtable={vtable:?})");
2424

25-
let (ty, poly_trait_ref) = self.tcx.erase_regions((ty, poly_trait_ref));
25+
let (ty, vtable) = self.tcx.erase_regions((ty, vtable));
2626

2727
// All vtables must be monomorphic, bail out otherwise.
2828
ensure_monomorphic_enough(*self.tcx, ty)?;
29-
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;
29+
ensure_monomorphic_enough(*self.tcx, vtable)?;
3030

3131
let salt = M::get_global_alloc_salt(self, None);
32-
let vtable_symbolic_allocation =
33-
self.tcx.reserve_and_set_vtable_alloc(ty, poly_trait_ref, salt);
32+
let vtable_symbolic_allocation = self.tcx.reserve_and_set_vtable_alloc(ty, vtable, salt);
3433
let vtable_ptr = self.global_root_pointer(Pointer::from(vtable_symbolic_allocation))?;
3534
Ok(vtable_ptr.into())
3635
}
@@ -64,17 +63,42 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
6463
/// expected trait type.
6564
pub(super) fn check_vtable_for_type(
6665
&self,
67-
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
68-
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
66+
allocated_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
67+
expected_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
6968
) -> InterpResult<'tcx> {
70-
let eq = match (expected_trait.principal(), vtable_trait) {
71-
(Some(a), Some(b)) => self.eq_in_param_env(a, b),
72-
(None, None) => true,
73-
_ => false,
74-
};
75-
if !eq {
76-
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
69+
let mut sorted_allocated_v: Vec<_> = allocated_vtable.without_auto_traits().collect();
70+
let mut sorted_expected_v: Vec<_> = expected_vtable.without_auto_traits().collect();
71+
// `skip_binder` here is okay because `stable_cmp` doesn't look at binders
72+
sorted_allocated_v.sort_by(|a, b| a.skip_binder().stable_cmp(*self.tcx, &b.skip_binder()));
73+
sorted_allocated_v.dedup();
74+
sorted_expected_v.sort_by(|a, b| a.skip_binder().stable_cmp(*self.tcx, &b.skip_binder()));
75+
sorted_expected_v.dedup();
76+
77+
if sorted_allocated_v.len() != sorted_expected_v.len() {
78+
throw_ub!(InvalidVTableTrait { allocated_vtable, expected_vtable });
79+
}
80+
81+
for (a_pred, b_pred) in std::iter::zip(sorted_allocated_v, sorted_expected_v) {
82+
let is_eq = match (a_pred.skip_binder(), b_pred.skip_binder()) {
83+
(
84+
ty::ExistentialPredicate::Trait(a_data),
85+
ty::ExistentialPredicate::Trait(b_data),
86+
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
87+
88+
(
89+
ty::ExistentialPredicate::Projection(a_data),
90+
ty::ExistentialPredicate::Projection(b_data),
91+
) => self.eq_in_param_env(a_pred.rebind(a_data), b_pred.rebind(b_data)),
92+
93+
_ => false,
94+
};
95+
if !is_eq {
96+
throw_ub!(InvalidVTableTrait { allocated_vtable, expected_vtable });
97+
}
7798
}
99+
100+
// FIXME: Should we validate auto traits here?
101+
78102
Ok(())
79103
}
80104

compiler/rustc_const_eval/src/interpret/validity.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,8 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
452452
self.path,
453453
Ub(DanglingIntPointer{ .. } | InvalidVTablePointer(..)) =>
454454
InvalidVTablePtr { value: format!("{vtable}") },
455-
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
456-
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
455+
Ub(InvalidVTableTrait { allocated_vtable, expected_vtable }) => {
456+
InvalidMetaWrongTrait { allocated_vtable, expected_vtable }
457457
},
458458
);
459459
}
@@ -1280,8 +1280,8 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
12801280
self.path,
12811281
// It's not great to catch errors here, since we can't give a very good path,
12821282
// but it's better than ICEing.
1283-
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
1284-
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
1283+
Ub(InvalidVTableTrait { allocated_vtable, expected_vtable }) => {
1284+
InvalidMetaWrongTrait { allocated_vtable, expected_vtable: *expected_vtable }
12851285
},
12861286
);
12871287
}

compiler/rustc_middle/src/mir/interpret/error.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,10 @@ pub enum UndefinedBehaviorInfo<'tcx> {
365365
InvalidVTablePointer(Pointer<AllocId>),
366366
/// Using a vtable for the wrong trait.
367367
InvalidVTableTrait {
368-
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
369-
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
368+
/// The vtable that was actually allocated into global memory.
369+
allocated_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
370+
/// The vtable that was expected at the point in MIR that it was accessed.
371+
expected_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
370372
},
371373
/// Using a string that is not valid UTF-8,
372374
InvalidStr(std::str::Utf8Error),
@@ -479,8 +481,10 @@ pub enum ValidationErrorKind<'tcx> {
479481
value: String,
480482
},
481483
InvalidMetaWrongTrait {
482-
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
483-
vtable_trait: Option<ty::PolyExistentialTraitRef<'tcx>>,
484+
/// The vtable that was actually allocated into global memory.
485+
allocated_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
486+
/// The vtable that was expected at the point in MIR that it was accessed.
487+
expected_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
484488
},
485489
InvalidMetaSliceTooLarge {
486490
ptr_kind: PointerKind,

compiler/rustc_middle/src/mir/interpret/mod.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,8 @@ impl<'s> AllocDecodingSession<'s> {
232232
}
233233
AllocDiscriminant::VTable => {
234234
trace!("creating vtable alloc ID");
235-
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
236-
let poly_trait_ref =
237-
<Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
235+
let ty = Decodable::decode(decoder);
236+
let poly_trait_ref = Decodable::decode(decoder);
238237
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
239238
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref, CTFE_ALLOC_SALT)
240239
}
@@ -259,7 +258,7 @@ pub enum GlobalAlloc<'tcx> {
259258
/// The alloc ID is used as a function pointer.
260259
Function { instance: Instance<'tcx> },
261260
/// This alloc ID points to a symbolic (not-reified) vtable.
262-
VTable(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>),
261+
VTable(Ty<'tcx>, &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>),
263262
/// The alloc ID points to a "lazy" static variable that did not get computed (yet).
264263
/// This is also used to break the cycle in recursive statics.
265264
Static(DefId),
@@ -293,7 +292,7 @@ impl<'tcx> GlobalAlloc<'tcx> {
293292
#[inline]
294293
pub fn unwrap_vtable(&self) -> (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>) {
295294
match *self {
296-
GlobalAlloc::VTable(ty, poly_trait_ref) => (ty, poly_trait_ref),
295+
GlobalAlloc::VTable(ty, vtable) => (ty, vtable.principal()),
297296
_ => bug!("expected vtable, got {:?}", self),
298297
}
299298
}
@@ -398,7 +397,7 @@ impl<'tcx> TyCtxt<'tcx> {
398397
pub fn reserve_and_set_vtable_alloc(
399398
self,
400399
ty: Ty<'tcx>,
401-
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
400+
poly_trait_ref: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
402401
salt: usize,
403402
) -> AllocId {
404403
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, poly_trait_ref), salt)

compiler/rustc_middle/src/mir/pretty.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -1536,11 +1536,8 @@ pub fn write_allocations<'tcx>(
15361536
// gracefully handle it and allow buggy rustc to be debugged via allocation printing.
15371537
None => write!(w, " (deallocated)")?,
15381538
Some(GlobalAlloc::Function { instance, .. }) => write!(w, " (fn: {instance})")?,
1539-
Some(GlobalAlloc::VTable(ty, Some(trait_ref))) => {
1540-
write!(w, " (vtable: impl {trait_ref} for {ty})")?
1541-
}
1542-
Some(GlobalAlloc::VTable(ty, None)) => {
1543-
write!(w, " (vtable: impl <auto trait> for {ty})")?
1539+
Some(GlobalAlloc::VTable(ty, vtable)) => {
1540+
write!(w, " (vtable: impl {vtable} for {ty})")?
15441541
}
15451542
Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => {
15461543
match tcx.eval_static_initializer(did) {

compiler/rustc_monomorphize/src/collector.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1175,8 +1175,8 @@ fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIt
11751175
output.push(create_fn_mono_item(tcx, instance, DUMMY_SP));
11761176
}
11771177
}
1178-
GlobalAlloc::VTable(ty, trait_ref) => {
1179-
let alloc_id = tcx.vtable_allocation((ty, trait_ref));
1178+
GlobalAlloc::VTable(ty, vtable) => {
1179+
let alloc_id = tcx.vtable_allocation((ty, vtable.principal()));
11801180
collect_alloc(tcx, alloc_id, output)
11811181
}
11821182
}

compiler/rustc_passes/src/reachable.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,17 @@ impl<'tcx> ReachableContext<'tcx> {
318318
));
319319
self.visit(instance.args);
320320
}
321-
GlobalAlloc::VTable(ty, trait_ref) => {
321+
GlobalAlloc::VTable(ty, vtable) => {
322322
self.visit(ty);
323323
// Manually visit to actually see the trait's `DefId`. Type visitors won't see it
324-
if let Some(trait_ref) = trait_ref {
324+
if let Some(trait_ref) = vtable.principal() {
325325
let ExistentialTraitRef { def_id, args } = trait_ref.skip_binder();
326326
self.visit_def_id(def_id, "", &"");
327327
self.visit(args);
328328
}
329+
330+
// FIXME: are we just skipping the auto traits here?
331+
// What is this reachability *for*?
329332
}
330333
GlobalAlloc::Memory(alloc) => self.propagate_from_alloc(alloc),
331334
}

compiler/rustc_smir/src/rustc_smir/convert/mir.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,9 @@ impl<'tcx> Stable<'tcx> for mir::interpret::GlobalAlloc<'tcx> {
712712
mir::interpret::GlobalAlloc::Function { instance, .. } => {
713713
GlobalAlloc::Function(instance.stable(tables))
714714
}
715-
mir::interpret::GlobalAlloc::VTable(ty, trait_ref) => {
716-
GlobalAlloc::VTable(ty.stable(tables), trait_ref.stable(tables))
715+
mir::interpret::GlobalAlloc::VTable(ty, vtable) => {
716+
// FIXME: Should we record the whole vtable?
717+
GlobalAlloc::VTable(ty.stable(tables), vtable.principal().stable(tables))
717718
}
718719
mir::interpret::GlobalAlloc::Static(def) => {
719720
GlobalAlloc::Static(tables.static_def(*def))

src/tools/miri/tests/fail/dyn-call-trait-mismatch.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ impl T1 for i32 {
1616
fn main() {
1717
let r = Box::new(0) as Box<dyn T1>;
1818
let r2: Box<dyn T2> = unsafe { std::mem::transmute(r) };
19-
r2.method2(); //~ERROR: using vtable for trait `T1` but trait `T2` was expected
19+
r2.method2(); //~ERROR: using vtable for `T1` but `T2` was expected
2020
}

src/tools/miri/tests/fail/dyn-call-trait-mismatch.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: using vtable for trait `T1` but trait `T2` was expected
1+
error: Undefined Behavior: using vtable for `T1` but `T2` was expected
22
--> tests/fail/dyn-call-trait-mismatch.rs:LL:CC
33
|
44
LL | r2.method2();
5-
| ^^^^^^^^^^^^ using vtable for trait `T1` but trait `T2` was expected
5+
| ^^^^^^^^^^^^ using vtable for `T1` but `T2` was expected
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)