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

fix fam issues #118

Merged
merged 4 commits into from
Feb 24, 2021
Merged

fix fam issues #118

merged 4 commits into from
Feb 24, 2021

Conversation

lauralt
Copy link
Contributor

@lauralt lauralt commented Feb 17, 2021

@lauralt
Copy link
Contributor Author

lauralt commented Feb 17, 2021

@jiangliu was there any other concern regarding #104 besides the Clone and PartialEq implementations?
I've noticed in vfio_ioctls the following structure:

struct vfio_region_info_with_cap {
    region_info: vfio_region_info,
    cap_info: __IncompleteArrayField<u8>,
}

which can be somehow seen as a fam struct even though the entries length is based on the region_info fields.

generate_fam_struct_impl indeed won't work for a structure that doesn't contain its own len field, but I don't think fixing this should be part of #104 since that macro is somehow of a helper (and I guess FamStruct can be implemented for the above structure as well). What do you think?

@lauralt lauralt force-pushed the max_len branch 2 times, most recently from 4b34117 to a2e43d0 Compare February 17, 2021 15:29
// The number of entries (self.as_slice().len()) can't be > T::max_len() since `self` is a
// valid `FamStructWrapper`.
let required_mem_allocator_capacity =
FamStructWrapper::<T>::mem_allocator_len(self.as_slice().len());
Copy link
Member

Choose a reason for hiding this comment

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

Should we clone the source FamStruct exactly by using "self.capacipy" instead of "self.as_slice().len()"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After having a chat with @alexandruag it turned out it would be better to avoid this kind of confusion and he suggested to add the Clone trait bound to FamStruct; this way we would simplify things a lot by simply cloning the mem_allocator. Does anyone have anything against this or see a potential problem with it?

Choose a reason for hiding this comment

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

Turns out that right now bindgen will not automatically derive/generate Clone a implementation for structures that contain incomplete array fields (which is a bit odd). I've added a bullet point about this to #120.

Regarding the first question, it looks like using self.capacity instead of self.as_slice().len() would cause us to push more elements than necessary below. If I'm not mistaken, I think clone is not necessarily supposed to preserve to capacity of containers (it doesn't for Vec IIRC).

src/fam.rs Outdated
// aligned `T`s.
let (fam_struct, other_fam_struct): (T, T) = unsafe {
(
std::ptr::read(self.as_fam_struct_ref()),
Copy link
Member

Choose a reason for hiding this comment

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

why do we need to std::ptr::read() instead of comparing self.as_fam_struct_ref() and other.as_fam_struct_ref() directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no need, sorry, updated

Comment on lines +981 to +984
foo.as_mut_fam_struct().length = 3;
foo.push(3).unwrap();
foo.push(14).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

if length is 3 shouldn't we push 3 elements?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, this was confusing for me as well, when you use push() it appends the element at the end of the entries collection which is considered as already having 3 elements (because of the above line). Anyway, this is not something that was added in this PR and I should've probably kept the tests simpler. Also, the whole fam module needs better documentation :D.

Comment on lines +984 to +987
assert_eq!(foo.as_slice().len(), 3 + 2);
assert_eq!(foo.as_slice()[3 + 0], 3);
assert_eq!(foo.as_slice()[3 + 1], 14);
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this 5? as slice should only provide the list of entries &[T::entry], right? I was expecting len to be 2 since we started off with 0 and pushed only 2.

Documentation for as_slice() says it gets the elements/entries. In your tests it seems it also comes back with contents of FAM struct packed as elements.

I am now confused 😄

Choose a reason for hiding this comment

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

Hmm, if the 5 is about the 3 + 2, that's because the original length is 3 (which are auto-pushed as zero-initialized elements as defined by the original interface) and we pushed 2. as_slice seems to work as intended here (i.e. we get the entries that we pushed earlier).

src/fam.rs Outdated
foo2.as_mut_fam_struct().flags
);
assert_eq!(foo.as_slice(), foo2.as_slice());
assert_eq!(foo2.as_slice().len(), foo2.as_mut_fam_struct().length as usize);
Copy link
Contributor

Choose a reason for hiding this comment

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

This also confuses me..

foo.as_slice().len() was checked to be 3 + 2, then here foo2.as_slice().len() is checked to be 3 (foo2.as_mut_fam_struct().length is 3).

src/fam.rs Show resolved Hide resolved
src/fam.rs Show resolved Hide resolved
// safety can't be violated by the ownership of `fam_struct`). It is also safe because we're
// trying to read a T from a `&T` that is pointing to a properly initialized and aligned T.
unsafe {
let fam_struct: T = std::ptr::read(self.as_fam_struct_ref());

Choose a reason for hiding this comment

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

what if the FAM header contains a pointer ? Can it happen ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question! As discussed offline, this might be possible, but T must be Default and from what I understand, implementing Default for a pointer becomes really awkward. Also, if a pointer is Copy, I wonder if ptr::read is actually unsafe :-?.
@jiangliu @alexandruag do you have some input on this? Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

FamStruct is an unsafe trait, which requires following conditions for safety

/// Trait for accessing properties of C defined FAM structures.
///
/// This is unsafe due to the number of constraints that aren't checked:
/// * the implementer should be a POD
/// * the implementor should contain a flexible array member of elements of type `Entry`
/// * `Entry` should be a POD

So it's illegal for an FamStruct implementor to host pointer fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was under the same impression, but after some googling/asking around, looks like a pointer is a POD. So isn't this actually right? :-?

Copy link
Member

Choose a reason for hiding this comment

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

I like this definition from https://stackoverflow.com/questions/45634083/is-there-a-concept-of-pod-types-in-rust:
POD: Types whose values can be duplicated simply by copying bits.

Choose a reason for hiding this comment

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

Sorry, my bad. Now I remember that the entire class was written with the assumption that T is memcopyable. It's just very strange that in C++ the following code prints true:

struct Pod {
    int *a;
};

int main()
{
    cout<<is_pod<Pod>::value<<endl;

    return 0;
}

Choose a reason for hiding this comment

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

Hi! Don't know what authoritative source is best to quote here, but POD structs can contain pointers, which are effectively just integers meant to be interpreted as addresses. A pointer holding a value does not come with any guarantees by itself, and all safety concerns are passed on to whomever actually uses that pointer for a memory operation later (since all fields of a POD struct are always accessible and can be modified, any method operating on them must take into account that every possible value can be encountered). So, even if the POD struct contains pointers (which is unlikely), that doesn't seem to break any of the current assumptions.

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 place to go would be the C++ standard. I don't own the hard copy, but the latest snapshot from the official repo loosely defines POD types through the template @serban300 used -

is_pod<T> is a Cpp17UnaryTypeTrait (20.15.2) with a base characteristic of true_type if T is a POD
type, and false_type otherwise. A POD class is a class that is both a trivial class and a standard-layout
class, and has no non-static data members of type non-POD class (or array thereof). A POD type is a
scalar type, a POD class, an array of such a type, or a cv-qualified version of one of these types.

[Note 1 : It is unspecified whether a closure type (7.5.5.2) is a POD type. — end note]

So my understanding here is in line with @alexandruag in the sense that scalar types are inherently POD, and since a pointer is basically just a number, it falls into this category.

I also found an unofficial definition that (more helpfully IMO) defines POD types as anything without a vtable.

Copy link

@serban300 serban300 Feb 19, 2021

Choose a reason for hiding this comment

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

Thanks for the clarifications. I agree, I don't have any concern here.

Copy link

Choose a reason for hiding this comment

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

As a rule of thumb anything in C++ is POD that can be used from C. So any C struct is almost by definition POD.

Personally, I find cppreference easier to read than the standard, but it's not authoritative.

All the fields from the FamStruct shuld be cloned instead of
just the len field and the entries slice.
Fixes: rust-vmm#85.

Signed-off-by: Laura Loghin <[email protected]>
PartialEq should compare all the fields from the FamStruct.
Fixes: rust-vmm#104.

Signed-off-by: Laura Loghin <[email protected]>
@jiangliu
Copy link
Member

@jiangliu was there any other concern regarding #104 besides the Clone and PartialEq implementations?
I've noticed in vfio_ioctls the following structure:

struct vfio_region_info_with_cap {
    region_info: vfio_region_info,
    cap_info: __IncompleteArrayField<u8>,
}

which can be somehow seen as a fam struct even though the entries length is based on the region_info fields.

generate_fam_struct_impl indeed won't work for a structure that doesn't contain its own len field, but I don't think fixing this should be part of #104 since that macro is somehow of a helper (and I guess FamStruct can be implemented for the above structure as well). What do you think?

When I was working on vfio-ioctls, I investigated the way to use generate_fam_struct_impl. But seems it's a little complex, so maybe it's ok to keep the hand-writing version for vfio-ioctls.

Signed-off-by: Andreea Florescu <[email protected]>
// The number of entries (self.as_slice().len()) can't be > T::max_len() since `self` is a
// valid `FamStructWrapper`.
let required_mem_allocator_capacity =
FamStructWrapper::<T>::mem_allocator_len(self.as_slice().len());

Choose a reason for hiding this comment

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

Turns out that right now bindgen will not automatically derive/generate Clone a implementation for structures that contain incomplete array fields (which is a bit odd). I've added a bullet point about this to #120.

Regarding the first question, it looks like using self.capacity instead of self.as_slice().len() would cause us to push more elements than necessary below. If I'm not mistaken, I think clone is not necessarily supposed to preserve to capacity of containers (it doesn't for Vec IIRC).

Comment on lines +984 to +987
assert_eq!(foo.as_slice().len(), 3 + 2);
assert_eq!(foo.as_slice()[3 + 0], 3);
assert_eq!(foo.as_slice()[3 + 1], 14);

Choose a reason for hiding this comment

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

Hmm, if the 5 is about the 3 + 2, that's because the original length is 3 (which are auto-pushed as zero-initialized elements as defined by the original interface) and we pushed 2. as_slice seems to work as intended here (i.e. we get the entries that we pushed earlier).

@andreeaflorescu andreeaflorescu merged commit 1f060f9 into rust-vmm:master Feb 24, 2021
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.

8 participants