Skip to content

Commit 182248a

Browse files
authored
Rollup merge of rust-lang#62245 - RalfJung:miri-extra-fn, r=eddyb,zackmdavis
Miri engine: support extra function (pointer) values We want to add basic support for `dlsym` in Miri (needed to run the latest version of `getrandom`). For that to work, `dlsym` needs to return *something* that can be stored in a function pointer and later called. So we add a new `ExtraFnVal` type to the `Machine` trait, and enable Miri's memory to associate allocation IDs with such values, so that `create_fn_alloc` and `get_fn` can work on *both* `Instance` (this is used for "normal" function pointers) and `ExtraFnVal`. Cc @oli-obk
2 parents a8f8c7c + ceb496c commit 182248a

File tree

12 files changed

+253
-122
lines changed

12 files changed

+253
-122
lines changed

src/librustc_mir/const_eval.rs

+11
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ impl interpret::MayLeak for ! {
316316
impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, 'tcx> {
317317
type MemoryKinds = !;
318318
type PointerTag = ();
319+
type ExtraFnVal = !;
319320

320321
type FrameExtra = ();
321322
type MemoryExtra = ();
@@ -370,6 +371,16 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
370371
}))
371372
}
372373

374+
fn call_extra_fn(
375+
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
376+
fn_val: !,
377+
_args: &[OpTy<'tcx>],
378+
_dest: Option<PlaceTy<'tcx>>,
379+
_ret: Option<mir::BasicBlock>,
380+
) -> InterpResult<'tcx> {
381+
match fn_val {}
382+
}
383+
373384
fn call_intrinsic(
374385
ecx: &mut InterpCx<'mir, 'tcx, Self>,
375386
instance: ty::Instance<'tcx>,

src/librustc_mir/interpret/cast.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc::mir::interpret::{
1111
};
1212
use rustc::mir::CastKind;
1313

14-
use super::{InterpCx, Machine, PlaceTy, OpTy, Immediate};
14+
use super::{InterpCx, Machine, PlaceTy, OpTy, Immediate, FnVal};
1515

1616
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
1717
fn type_is_fat_ptr(&self, ty: Ty<'tcx>) -> bool {
@@ -86,7 +86,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
8686
def_id,
8787
substs,
8888
).ok_or_else(|| InterpError::TooGeneric.into());
89-
let fn_ptr = self.memory.create_fn_alloc(instance?);
89+
let fn_ptr = self.memory.create_fn_alloc(FnVal::Instance(instance?));
9090
self.write_scalar(Scalar::Ptr(fn_ptr.into()), dest)?;
9191
}
9292
_ => bug!("reify fn pointer on {:?}", src.layout.ty),
@@ -115,7 +115,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
115115
substs,
116116
ty::ClosureKind::FnOnce,
117117
);
118-
let fn_ptr = self.memory.create_fn_alloc(instance);
118+
let fn_ptr = self.memory.create_fn_alloc(FnVal::Instance(instance));
119119
let val = Immediate::Scalar(Scalar::Ptr(fn_ptr.into()).into());
120120
self.write_immediate(val, dest)?;
121121
}

src/librustc_mir/interpret/machine.rs

+15
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ pub trait Machine<'mir, 'tcx>: Sized {
6767
/// The `default()` is used for pointers to consts, statics, vtables and functions.
6868
type PointerTag: ::std::fmt::Debug + Copy + Eq + Hash + 'static;
6969

70+
/// Machines can define extra (non-instance) things that represent values of function pointers.
71+
/// For example, Miri uses this to return a fucntion pointer from `dlsym`
72+
/// that can later be called to execute the right thing.
73+
type ExtraFnVal: ::std::fmt::Debug + Copy;
74+
7075
/// Extra data stored in every call frame.
7176
type FrameExtra;
7277

@@ -119,6 +124,16 @@ pub trait Machine<'mir, 'tcx>: Sized {
119124
ret: Option<mir::BasicBlock>,
120125
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;
121126

127+
/// Execute `fn_val`. it is the hook's responsibility to advance the instruction
128+
/// pointer as appropriate.
129+
fn call_extra_fn(
130+
ecx: &mut InterpCx<'mir, 'tcx, Self>,
131+
fn_val: Self::ExtraFnVal,
132+
args: &[OpTy<'tcx, Self::PointerTag>],
133+
dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
134+
ret: Option<mir::BasicBlock>,
135+
) -> InterpResult<'tcx>;
136+
122137
/// Directly process an intrinsic without pushing a stack frame.
123138
/// If this returns successfully, the engine will take care of jumping to the next block.
124139
fn call_intrinsic(

src/librustc_mir/interpret/memory.rs

+84-36
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ pub enum AllocCheck {
5454
MaybeDead,
5555
}
5656

57+
/// The value of a function pointer.
58+
#[derive(Debug, Copy, Clone)]
59+
pub enum FnVal<'tcx, Other> {
60+
Instance(Instance<'tcx>),
61+
Other(Other),
62+
}
63+
64+
impl<'tcx, Other> FnVal<'tcx, Other> {
65+
pub fn as_instance(self) -> InterpResult<'tcx, Instance<'tcx>> {
66+
match self {
67+
FnVal::Instance(instance) =>
68+
Ok(instance),
69+
FnVal::Other(_) =>
70+
err!(MachineError(
71+
format!("Expected instance function pointer, got 'other' pointer")
72+
)),
73+
}
74+
}
75+
}
76+
5777
// `Memory` has to depend on the `Machine` because some of its operations
5878
// (e.g., `get`) call a `Machine` hook.
5979
pub struct Memory<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -69,16 +89,20 @@ pub struct Memory<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
6989
// FIXME: this should not be public, but interning currently needs access to it
7090
pub(super) alloc_map: M::MemoryMap,
7191

92+
/// Map for "extra" function pointers.
93+
extra_fn_ptr_map: FxHashMap<AllocId, M::ExtraFnVal>,
94+
7295
/// To be able to compare pointers with NULL, and to check alignment for accesses
7396
/// to ZSTs (where pointers may dangle), we keep track of the size even for allocations
7497
/// that do not exist any more.
98+
// FIXME: this should not be public, but interning currently needs access to it
7599
pub(super) dead_alloc_map: FxHashMap<AllocId, (Size, Align)>,
76100

77101
/// Extra data added by the machine.
78102
pub extra: M::MemoryExtra,
79103

80104
/// Lets us implement `HasDataLayout`, which is awfully convenient.
81-
pub(super) tcx: TyCtxtAt<'tcx>,
105+
pub tcx: TyCtxtAt<'tcx>,
82106
}
83107

84108
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for Memory<'mir, 'tcx, M> {
@@ -98,6 +122,7 @@ where
98122
fn clone(&self) -> Self {
99123
Memory {
100124
alloc_map: self.alloc_map.clone(),
125+
extra_fn_ptr_map: self.extra_fn_ptr_map.clone(),
101126
dead_alloc_map: self.dead_alloc_map.clone(),
102127
extra: (),
103128
tcx: self.tcx,
@@ -109,6 +134,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
109134
pub fn new(tcx: TyCtxtAt<'tcx>, extra: M::MemoryExtra) -> Self {
110135
Memory {
111136
alloc_map: M::MemoryMap::default(),
137+
extra_fn_ptr_map: FxHashMap::default(),
112138
dead_alloc_map: FxHashMap::default(),
113139
extra,
114140
tcx,
@@ -120,8 +146,21 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
120146
ptr.with_tag(M::tag_static_base_pointer(ptr.alloc_id, &self))
121147
}
122148

123-
pub fn create_fn_alloc(&mut self, instance: Instance<'tcx>) -> Pointer<M::PointerTag> {
124-
let id = self.tcx.alloc_map.lock().create_fn_alloc(instance);
149+
pub fn create_fn_alloc(
150+
&mut self,
151+
fn_val: FnVal<'tcx, M::ExtraFnVal>,
152+
) -> Pointer<M::PointerTag>
153+
{
154+
let id = match fn_val {
155+
FnVal::Instance(instance) => self.tcx.alloc_map.lock().create_fn_alloc(instance),
156+
FnVal::Other(extra) => {
157+
// FIXME(RalfJung): Should we have a cache here?
158+
let id = self.tcx.alloc_map.lock().reserve();
159+
let old = self.extra_fn_ptr_map.insert(id, extra);
160+
assert!(old.is_none());
161+
id
162+
}
163+
};
125164
self.tag_static_base_pointer(Pointer::from(id))
126165
}
127166

@@ -495,56 +534,65 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
495534
id: AllocId,
496535
liveness: AllocCheck,
497536
) -> InterpResult<'static, (Size, Align)> {
537+
// Regular allocations.
498538
if let Ok(alloc) = self.get(id) {
499539
return Ok((Size::from_bytes(alloc.bytes.len() as u64), alloc.align));
500540
}
501-
// can't do this in the match argument, we may get cycle errors since the lock would get
502-
// dropped after the match.
541+
// Function pointers.
542+
if let Ok(_) = self.get_fn_alloc(id) {
543+
return if let AllocCheck::Dereferencable = liveness {
544+
// The caller requested no function pointers.
545+
err!(DerefFunctionPointer)
546+
} else {
547+
Ok((Size::ZERO, Align::from_bytes(1).unwrap()))
548+
};
549+
}
550+
// Foreign statics.
551+
// Can't do this in the match argument, we may get cycle errors since the lock would
552+
// be held throughout the match.
503553
let alloc = self.tcx.alloc_map.lock().get(id);
504-
// Could also be a fn ptr or extern static
505554
match alloc {
506-
Some(GlobalAlloc::Function(..)) => {
507-
if let AllocCheck::Dereferencable = liveness {
508-
// The caller requested no function pointers.
509-
err!(DerefFunctionPointer)
510-
} else {
511-
Ok((Size::ZERO, Align::from_bytes(1).unwrap()))
512-
}
513-
}
514-
// `self.get` would also work, but can cause cycles if a static refers to itself
515555
Some(GlobalAlloc::Static(did)) => {
516-
// The only way `get` couldn't have worked here is if this is an extern static
517556
assert!(self.tcx.is_foreign_item(did));
518557
// Use size and align of the type
519558
let ty = self.tcx.type_of(did);
520559
let layout = self.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap();
521-
Ok((layout.size, layout.align.abi))
560+
return Ok((layout.size, layout.align.abi));
522561
}
523-
_ => {
524-
if let Ok(alloc) = self.get(id) {
525-
Ok((Size::from_bytes(alloc.bytes.len() as u64), alloc.align))
526-
}
527-
else if let AllocCheck::MaybeDead = liveness {
528-
// Deallocated pointers are allowed, we should be able to find
529-
// them in the map.
530-
Ok(*self.dead_alloc_map.get(&id)
531-
.expect("deallocated pointers should all be recorded in `dead_alloc_map`"))
532-
} else {
533-
err!(DanglingPointerDeref)
534-
}
535-
},
562+
_ => {}
563+
}
564+
// The rest must be dead.
565+
if let AllocCheck::MaybeDead = liveness {
566+
// Deallocated pointers are allowed, we should be able to find
567+
// them in the map.
568+
Ok(*self.dead_alloc_map.get(&id)
569+
.expect("deallocated pointers should all be recorded in `dead_alloc_map`"))
570+
} else {
571+
err!(DanglingPointerDeref)
536572
}
537573
}
538574

539-
pub fn get_fn(&self, ptr: Pointer<M::PointerTag>) -> InterpResult<'tcx, Instance<'tcx>> {
575+
fn get_fn_alloc(&self, id: AllocId) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
576+
trace!("reading fn ptr: {}", id);
577+
if let Some(extra) = self.extra_fn_ptr_map.get(&id) {
578+
Ok(FnVal::Other(*extra))
579+
} else {
580+
match self.tcx.alloc_map.lock().get(id) {
581+
Some(GlobalAlloc::Function(instance)) => Ok(FnVal::Instance(instance)),
582+
_ => Err(InterpError::ExecuteMemory.into()),
583+
}
584+
}
585+
}
586+
587+
pub fn get_fn(
588+
&self,
589+
ptr: Scalar<M::PointerTag>,
590+
) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
591+
let ptr = self.force_ptr(ptr)?; // We definitely need a pointer value.
540592
if ptr.offset.bytes() != 0 {
541593
return err!(InvalidFunctionPointer);
542594
}
543-
trace!("reading fn ptr: {}", ptr.alloc_id);
544-
match self.tcx.alloc_map.lock().get(ptr.alloc_id) {
545-
Some(GlobalAlloc::Function(instance)) => Ok(instance),
546-
_ => Err(InterpError::ExecuteMemory.into()),
547-
}
595+
self.get_fn_alloc(ptr.alloc_id)
548596
}
549597

550598
pub fn mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> {

src/librustc_mir/interpret/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub use self::eval_context::{
2424

2525
pub use self::place::{Place, PlaceTy, MemPlace, MPlaceTy};
2626

27-
pub use self::memory::{Memory, MemoryKind, AllocCheck};
27+
pub use self::memory::{Memory, MemoryKind, AllocCheck, FnVal};
2828

2929
pub use self::machine::{Machine, AllocMap, MayLeak};
3030

src/librustc_mir/interpret/terminator.rs

+21-14
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use rustc::ty::layout::{self, TyLayout, LayoutOf};
66
use syntax::source_map::Span;
77
use rustc_target::spec::abi::Abi;
88

9-
use rustc::mir::interpret::{InterpResult, PointerArithmetic, InterpError, Scalar};
109
use super::{
11-
InterpCx, Machine, Immediate, OpTy, ImmTy, PlaceTy, MPlaceTy, StackPopCleanup
10+
InterpResult, PointerArithmetic, InterpError, Scalar,
11+
InterpCx, Machine, Immediate, OpTy, ImmTy, PlaceTy, MPlaceTy, StackPopCleanup, FnVal,
1212
};
1313

1414
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
@@ -76,16 +76,16 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
7676
};
7777

7878
let func = self.eval_operand(func, None)?;
79-
let (fn_def, abi) = match func.layout.ty.sty {
79+
let (fn_val, abi) = match func.layout.ty.sty {
8080
ty::FnPtr(sig) => {
8181
let caller_abi = sig.abi();
82-
let fn_ptr = self.force_ptr(self.read_scalar(func)?.not_undef()?)?;
83-
let instance = self.memory.get_fn(fn_ptr)?;
84-
(instance, caller_abi)
82+
let fn_ptr = self.read_scalar(func)?.not_undef()?;
83+
let fn_val = self.memory.get_fn(fn_ptr)?;
84+
(fn_val, caller_abi)
8585
}
8686
ty::FnDef(def_id, substs) => {
8787
let sig = func.layout.ty.fn_sig(*self.tcx);
88-
(self.resolve(def_id, substs)?, sig.abi())
88+
(FnVal::Instance(self.resolve(def_id, substs)?), sig.abi())
8989
},
9090
_ => {
9191
let msg = format!("can't handle callee of type {:?}", func.layout.ty);
@@ -94,7 +94,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
9494
};
9595
let args = self.eval_operands(args)?;
9696
self.eval_fn_call(
97-
fn_def,
97+
fn_val,
9898
terminator.source_info.span,
9999
abi,
100100
&args[..],
@@ -228,14 +228,21 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
228228
/// Call this function -- pushing the stack frame and initializing the arguments.
229229
fn eval_fn_call(
230230
&mut self,
231-
instance: ty::Instance<'tcx>,
231+
fn_val: FnVal<'tcx, M::ExtraFnVal>,
232232
span: Span,
233233
caller_abi: Abi,
234234
args: &[OpTy<'tcx, M::PointerTag>],
235235
dest: Option<PlaceTy<'tcx, M::PointerTag>>,
236236
ret: Option<mir::BasicBlock>,
237237
) -> InterpResult<'tcx> {
238-
trace!("eval_fn_call: {:#?}", instance);
238+
trace!("eval_fn_call: {:#?}", fn_val);
239+
240+
let instance = match fn_val {
241+
FnVal::Instance(instance) => instance,
242+
FnVal::Other(extra) => {
243+
return M::call_extra_fn(self, extra, args, dest, ret);
244+
}
245+
};
239246

240247
match instance.def {
241248
ty::InstanceDef::Intrinsic(..) => {
@@ -431,8 +438,8 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
431438
self.tcx.data_layout.pointer_align.abi,
432439
)?.expect("cannot be a ZST");
433440
let fn_ptr = self.memory.get(vtable_slot.alloc_id)?
434-
.read_ptr_sized(self, vtable_slot)?.to_ptr()?;
435-
let instance = self.memory.get_fn(fn_ptr)?;
441+
.read_ptr_sized(self, vtable_slot)?.not_undef()?;
442+
let drop_fn = self.memory.get_fn(fn_ptr)?;
436443

437444
// `*mut receiver_place.layout.ty` is almost the layout that we
438445
// want for args[0]: We have to project to field 0 because we want
@@ -447,7 +454,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
447454
});
448455
trace!("Patched self operand to {:#?}", args[0]);
449456
// recurse with concrete function
450-
self.eval_fn_call(instance, span, caller_abi, &args, dest, ret)
457+
self.eval_fn_call(drop_fn, span, caller_abi, &args, dest, ret)
451458
}
452459
}
453460
}
@@ -482,7 +489,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
482489
let dest = MPlaceTy::dangling(self.layout_of(ty)?, self);
483490

484491
self.eval_fn_call(
485-
instance,
492+
FnVal::Instance(instance),
486493
span,
487494
Abi::Rust,
488495
&[arg.into()],

0 commit comments

Comments
 (0)