Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ensure that integers cast to pointers will never point at a valid alloc, not even the zst alloc #81

Merged
merged 19 commits into from
Nov 17, 2016

Conversation

oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Nov 10, 2016

fixes #77

@@ -73,7 +73,7 @@ impl Pointer {
// FIXME(solson): Integer pointers should use u64, not usize. Target pointers can be larger
// than host usize.
pub fn from_int(i: usize) -> Self {
Pointer::new(ZST_ALLOC_ID, i)
Pointer::new(NEVER_ALLOC_ID, i)
Copy link
Contributor Author

@oli-obk oli-obk Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I did this change to fix the assume bug, which then caused drop impls to fail: https://github.com/solson/miri/pull/81/files#diff-8f4a840e817b018e1d74153888641b27L642 and int to pointer cast error messages to show up as dangling pointers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine. I might have a plan to refactor Pointer::from_int out of existence, but I haven't taken a close enough look yet.

@solson
Copy link
Member

solson commented Nov 11, 2016

Sorry for the delay. I'll try to do a full review this evening.

@oli-obk
Copy link
Contributor Author

oli-obk commented Nov 11, 2016

No hurry. I'm not gonna get to it before monday

Copy link
Member

@solson solson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I just have a few questions.

@@ -61,7 +61,7 @@ impl<'tcx> Error for EvalError<'tcx> {
EvalError::DanglingPointerDeref =>
"dangling pointer was dereferenced",
EvalError::InvalidFunctionPointer =>
"tried to use a pointer as a function pointer",
"tried to use an integer pointer as a function pointer",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this apply to general pointers casted to function pointers any more? The name InvalidFunctionPointer seems too general for the new error message.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oli-obk pinging to make sure you don't miss this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing changed here, the error message was outdated way before this pr, we can adjust the variant name to match that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of error do we actually get when treating a regular pointer as a function pointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    let b = Box::new(42);
    let g = unsafe {
        std::mem::transmute::<&usize, &fn(i32)>(&b)
    };

    (*g)(42) //~ ERROR tried to use an integer pointer or a dangling pointer as a function pointer

We can't do transmute(42) because primvals panic in some cases right now. I think we should eliminate the expect functions and have the try functions return an EvalResult instead.

  10:     0x55e4c4ab7a18 - <core::option::Option<T>>::expect::hda65b98614e3f1e8
                        at /buildslave/rust-buildbot/slave/nightly-dist-rustc-linux/build/obj/../src/libcore/option.rs:293
  11:     0x55e4c4bf53c3 - miri::primval::PrimVal::expect_fn_ptr::h87524394959696a3
                        at /home/ws/ca8159/Projects/rust/miri/src/primval.rs:198
  12:     0x55e4c4ba9ae7 - miri::interpreter::terminator::<impl miri::interpreter::EvalContext<'a, 'tcx>>::eval_terminator::h1e9da0fab75dcb3c
                        at /home/ws/ca8159/Projects/rust/miri/src/interpreter/terminator/mod.rs:88
  13:     0x55e4c4b960c5 - miri::interpreter::step::<impl miri::interpreter::EvalContext<'a, 'tcx>>::terminator::h0a43b45ccf9ebe32
                        at /home/ws/ca8159/Projects/rust/miri/src/interpreter/step.rs:98
  14:     0x55e4c4b95659 - miri::interpreter::step::<impl miri::interpreter::EvalContext<'a, 'tcx>>::step::ha153e9fdef721ba9
                        at /home/ws/ca8159/Projects/rust/miri/src/interpreter/step.rs:69
  15:     0x55e4c4be5fb8 - miri::interpreter::eval_main::h502fc7147a3a0d58
                        at /home/ws/ca8159/Projects/rust/miri/src/interpreter/mod.rs:1664
  16:     0x55e4c4a8c843 - <miri::MiriCompilerCalls as rustc_driver::CompilerCalls<'a>>::build_controller::{{closure}}::h01b87c9edeea1ee3
                        at /home/ws/ca8159/Projects/rust/miri/src/bin/miri.rs:72

@@ -73,7 +73,7 @@ impl Pointer {
// FIXME(solson): Integer pointers should use u64, not usize. Target pointers can be larger
// than host usize.
pub fn from_int(i: usize) -> Self {
Pointer::new(ZST_ALLOC_ID, i)
Pointer::new(NEVER_ALLOC_ID, i)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine. I might have a plan to refactor Pointer::from_int out of existence, but I haven't taken a close enough look yet.

@@ -523,7 +519,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_value(value, dest, value_ty)?;
} else {
assert_eq!(operands.len(), 0);
let zero = self.isize_primval(0);
let value_size = self.type_size(dest_ty).expect("pointer types are sized");
let zero = PrimVal::from_int_with_size(0, value_size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? I think we can assume isize is pointer sized, and isize_primval does mean the target isize.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want an isize here. The rawnullablepointer opt also happens for nonpointers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, right, for NonZero things that aren't pointer-sized? I hate the RawNullablePointer names in layout...


// FIXME(solson)
let dest = self.force_allocation(dest)?.to_ptr();

let dest = dest.offset(offset.bytes() as isize);
try!(self.memory.write_isize(dest, 0));
let dest_size = self.type_size(ty).unwrap_or(self.memory.pointer_size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what cases can this default to self.memory.pointer_size()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the unsized case. Since any unsized value is a pointer to the actual sized value and this optimization only cares about the pointer part, not the extra part

Copy link
Member

@solson solson Nov 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What unsized case? ty is the type of the non-zero field that is used for the discriminant in this optimization, right? How can that field be unsized?

EDIT: Nevermind, I now see the conversation lower down; let's continue there.

let nonnull = adt_ptr.offset(offset.bytes() as isize);
self.read_nonnull_discriminant_value(nonnull, nndiscr)?
// only the pointer part of a fat pointer is used for this space optimization
let discr_size = self.type_size(ty).unwrap_or(self.memory.pointer_size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here. When can the nonnull field be a unsized?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be, but the MIR ends up giving us an index path that will give us the first field of the fat pointer, which ends up being te unsized value. Not sure if the code giving us the type path is wrong or if this is intended. I'll check and get back to you

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first field of the fat pointer is the thin data pointer. This is a GEP field path, it will not deref nested pointers, just offset into a flat structure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. It sounds like we'd need to adjust the type, then, to match @eddyb's point. Alternately, maybe it can return the size instead of the type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

match (field_index, &ty.sty) {
(1, &ty::TySlice(_)) => Ok(self.tcx.types.usize),
(1, &ty::TyTrait(_)) |
(0, _) => Ok(self.tcx.mk_imm_ptr(self.tcx.types.never)),
Copy link
Member

@eddyb eddyb Nov 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use a pointer to u8 to avoid any other implications. Btw you need to use struct_tail to get the right ty to even check here, e.g. for Struct<str> - oh you also need to handle str.
Also I don't think the sized case above makes sense? We only treat a fat pointer like a pair.

}
assert!(!self.type_is_sized(ty));
match (field_index, &self.tcx.struct_tail(ty).sty) {
(1, &ty::TySlice(_)) => Ok(self.tcx.types.usize),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also mention TyStr... However, neither [T] nor str's length fields can be used for the optimization.
It's probably okay to keep it? I'm not sure. Also, the assert!(!self.type_is_sized(ty)); can be removed since you use struct_tail, all you have to do is match on TyTrait / TySlice / TyStr specifically for the pointer field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably okay to keep it?

I'd prefer to stick to cases we know can happen (field_index == 0, or more specifically the constant that says which fat pointer field is the thin pointer) and assert that nothing else happens. Since we can't use length fields, I'd remove any code that handles those cases. Vtable pointers seem plausible, but do we even use those?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep vtable pointers since we might use them whenever we get more thorough niche optimizations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other values used as discriminants? The slice length might be used in optimizations for nonzst slices, which can't be usize::max_value() long.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I take back what I said since this is the general get_field_ty, not specific to the non-null optimizations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that this is the general get_field_ty also means we should guard against thin pointer types one way or another, perhaps by adding a match guard so thin pointers go to the "can't handle type" case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oli-obk Interesting point but we never considered it AFAIK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sized assertion does this check

@oli-obk
Copy link
Contributor Author

oli-obk commented Nov 13, 2016

we actually pass a lot more tests now: #55

_ => bug!("invalid fat pointee type"),
}
assert!(!self.type_is_sized(ty));
match (field_index, &self.tcx.struct_tail(ty).sty) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the & here and on the patterns is redundant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, the value is not copyable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eddyb Is this a problem because of the tuple? Not being copyable for sure wouldn't be a problem when matching directly, because the patterns don't try to copy (or move, or bind at all) anything in the TyS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, the error is on building the tuple, not matching... you'd need language support for matching two things at once without a tuple to make it work the way I expected.

@oli-obk
Copy link
Contributor Author

oli-obk commented Nov 15, 2016

ok, everything has been addressed and fixed. I did some changes to how Primval pointers are handled to make sure that there's no difference between reading a primval from memory and transmuting a primval

}

_ => bug!("can't deref non pointer types"),
match self.tcx.struct_tail(pointee_type).sty {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so much clearer. :D

if let Some(ptr) = val.try_as_ptr() {
return self.write_ptr(dest, ptr);
if let Some(alloc_id) = val.relocation {
return self.write_ptr(dest, Pointer::new(alloc_id, val.bits as usize));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not your mistake, just making a note for posterity: this as usize is wrong when emulating a 64-bit target on a 32-bit host. I have a TODO on Pointer for changing the usize to u64.

@solson
Copy link
Member

solson commented Nov 17, 2016

The trend toward making decisions in the code based on Tys rather than PrimValKind or other Miri things like relocations is a great trend. It will help with some of my refactorings and seems to be making the code clearer.

@solson solson merged commit 9322dec into rust-lang:master Nov 17, 2016
erickt pushed a commit to erickt/miri that referenced this pull request Feb 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

triggering assume in a small example that rustc compiles fine
3 participants