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

[Merged by Bors] - bevy_reflect: ReflectFromPtr to create &dyn Reflect from a *const () #4475

Closed

Conversation

jakobhellermann
Copy link
Contributor

@jakobhellermann jakobhellermann commented Apr 14, 2022

Objective

#4447 adds functions that can fetch resources/components as *const () ptr by providing the ComponentId. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a &dyn Reflect which is the pointer + a pointer to the VTable of the Reflect impl.

By adding a ReflectFromPtr type that is included in the type type registration when deriving Reflect, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection:

#[derive(Reflect)]
struct StringResource {
    value: String
}
local res_id = world:resource_id_by_name("example::StringResource")
local res = world:resource(res_id)

print(res.value)

Solution

  1. add a ReflectFromPtr type with a FromType<T: Reflect> implementation and the following methods:
  • pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect
  • pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect

Safety requirements of the methods are that you need to check that the ReflectFromPtr was constructed for the correct type.

  1. add that type to the TypeRegistration in the GetTypeRegistration impl generated by #[derive(Reflect)].
    This is different to other reflected traits because it doesn't need #[reflect(ReflectReflectFromPtr)] which IMO should be there by default.

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Apr 14, 2022
@jakobhellermann jakobhellermann added C-Feature A new feature, making something new possible A-Reflection Runtime information about types and removed S-Needs-Triage This issue needs to be labelled labels Apr 14, 2022
crates/bevy_reflect/src/type_registry.rs Show resolved Hide resolved
crates/bevy_reflect/src/type_registry.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/type_registry.rs Outdated Show resolved Hide resolved
@jakobhellermann
Copy link
Contributor Author

I changed the unsafe functions to return a *const dyn Reflect instead of a &dyn Reflect with an arbitrary caller-chosen lifetime. This is safer because now the caller must explicitly dereference the pointer and think about the lifetime it is valid for instead of rust automatically choosing a lifetime which is big enough for its uses.

-/// Safety: correct type and correct chosen lifetime
-pub unsafe fn to_reflect_ptr<'a>(&self, val: *const ()) -> &'a dyn Reflect
+/// Safety: correct type
+pub unsafe fn to_reflect_ptr(&self, val: *const ()) -> *const dyn Reflect

Copy link
Member

@MrGVSV MrGVSV left a comment

Choose a reason for hiding this comment

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

LGTM! Unfortunately, I don't feel confident enough in Rust pointers to approve it myself, though :/

crates/bevy_reflect/src/type_registry.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/type_registry.rs Show resolved Hide resolved
@jakobhellermann
Copy link
Contributor Author

I noticed a problem in this PR where a malicious user could

impl GetTypeRegisteration for T {
  fn get_type_registration() {
    let mut reg = TypeRegistration::of::<T>();
    reg.insert::<<ReflectFromPtr as FromType<DefinitelyNotT>>::from_type());
    reg
  }
}

Which would make it impossible to use to_reflect_ptr soundly because as a user you cannot rely that the type you fetched it for is the correct one, and the type check in to_reflect is wrong.

This can only be fixed by tweaking the type registration process so that the TypeRegistry maintains the invariant that and TypeData inserted will be 100% for the correct type.

@jakobhellermann
Copy link
Contributor Author

blocked on #4567

bors bot pushed a commit that referenced this pull request May 4, 2022
# Objective

The pointer types introduced in #3001 are useful not just in `bevy_ecs`, but also in crates like `bevy_reflect` (#4475) or even outside of bevy.

## Solution

Extract `Ptr<'a>`, `PtrMut<'a>`, `OwnedPtr<'a>`, `ThinSlicePtr<'a, T>` and `UnsafeCellDeref` from `bevy_ecs::ptr` into `bevy_ptr`.

**Note:** `bevy_ecs` still reexports the `bevy_ptr` as `bevy_ecs::ptr` so that crates like `bevy_transform` can use the `Bundle` derive without needing to depend on `bevy_ptr` themselves.
@jakobhellermann
Copy link
Contributor Author

Updated the PR to bevy_ptr. And perhaps "blocked on #4567" is a bit much, the PR is still useful for cases where you know the type of the value, it's just that if you don't you can't really trust that the ReflectFromPtr was created for the correct type.

robtfm pushed a commit to robtfm/bevy that referenced this pull request May 10, 2022
# Objective

The pointer types introduced in bevyengine#3001 are useful not just in `bevy_ecs`, but also in crates like `bevy_reflect` (bevyengine#4475) or even outside of bevy.

## Solution

Extract `Ptr<'a>`, `PtrMut<'a>`, `OwnedPtr<'a>`, `ThinSlicePtr<'a, T>` and `UnsafeCellDeref` from `bevy_ecs::ptr` into `bevy_ptr`.

**Note:** `bevy_ecs` still reexports the `bevy_ptr` as `bevy_ecs::ptr` so that crates like `bevy_transform` can use the `Bundle` derive without needing to depend on `bevy_ptr` themselves.
@MrGVSV
Copy link
Member

MrGVSV commented May 12, 2022

Ping @jakobhellermann this branch likely has merge conflicts from #4712. To resolve, simply move your line of code in bevy_reflect_derive to the new registration.rs file.

Copy link
Member

@MrGVSV MrGVSV 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 to me (though, again, not super familiar with pointer manipulation in Rust so take my approval with a grain of salt haha).

crates/bevy_reflect/src/type_registry.rs Outdated Show resolved Hide resolved
crates/bevy_reflect/src/type_registry.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@PROMETHIA-27 PROMETHIA-27 left a comment

Choose a reason for hiding this comment

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

A bit spooky, but I can't see any Undefined Behavior unless you break the type-safety promise. Definitely going to appreciate this for scripting.

crates/bevy_reflect/src/impls/std.rs Outdated Show resolved Hide resolved
@alice-i-cecile alice-i-cecile added X-Controversial There is active debate or serious implications around merging this PR S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it labels May 17, 2022
@alice-i-cecile
Copy link
Member

as_reflect and as_reflect_mut seem wholly unnecessary, given &'a T where T: Reflect can be coerced to &'a dyn Reflect without any additional help.

Pulling this comment by @TheRawMeatball up a level; we should block on that feedback being resolved in some form or another.

@PROMETHIA-27
Copy link
Contributor

Hm, you could just replace the body of those functions with their input couldn't you... why are they there?

@jakobhellermann
Copy link
Contributor Author

hm so they were meant as a safer alternative for when you don't need pointers involved but in that case you can just val as &dyn Reflect so the methods are pretty useless. I'll remove them.

@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 17, 2022
exjam pushed a commit to exjam/bevy that referenced this pull request May 22, 2022
# Objective

The pointer types introduced in bevyengine#3001 are useful not just in `bevy_ecs`, but also in crates like `bevy_reflect` (bevyengine#4475) or even outside of bevy.

## Solution

Extract `Ptr<'a>`, `PtrMut<'a>`, `OwnedPtr<'a>`, `ThinSlicePtr<'a, T>` and `UnsafeCellDeref` from `bevy_ecs::ptr` into `bevy_ptr`.

**Note:** `bevy_ecs` still reexports the `bevy_ptr` as `bevy_ecs::ptr` so that crates like `bevy_transform` can use the `Bundle` derive without needing to depend on `bevy_ptr` themselves.
Copy link
Member

@cart cart 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 to me! One conversation I'd like to have, but I won't block on it.
(Please reply even after this is merged)

// not required in this situation because we no nobody messed with the TypeRegistry,
// but in the general case somebody could have replaced the ReflectFromPtr with an
// instance for another type, so then we'd need to check that the type is the expected one
assert_eq!(reflect_from_ptr.type_id(), std::any::TypeId::of::<Foo>());
Copy link
Member

@cart cart Jul 19, 2022

Choose a reason for hiding this comment

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

I don't think we're in a place where we can treat "arbitrary rust code running in the users' program" as untrusted. Given that rust can do "anything", if there is arbitrary malicious code in a users' program, it is already compromised no matter what we do. Things like public/private visibility don't mean anything here when you can create a mirror struct that provides access and then unsafely cast a pointer to that struct.

We can only provide these guarantees in a sandbox with tight controls over data that passes the boundary. This api won't provide that, so idk if I see the point here (feel free to tell me if/how I'm wrong).

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I thought we were trying to be sound? In that case, we'd treat all safe code as untrusted, and make sure that safe code cannot add requirements to an unsafe function.

Basically, this comment really really confuses me.

@cart
Copy link
Member

cart commented Jul 19, 2022

bors r+

bors bot pushed a commit that referenced this pull request Jul 19, 2022
…t ()` (#4475)

# Objective

#4447 adds functions that can fetch resources/components as `*const ()` ptr by providing the `ComponentId`. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a `&dyn Reflect` which is the pointer + a pointer to the VTable of the `Reflect` impl.

By adding a `ReflectFromPtr` type that is included in the type type registration when deriving `Reflect`, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection:

```rust
#[derive(Reflect)]
struct StringResource {
    value: String
}
```

```lua
local res_id = world:resource_id_by_name("example::StringResource")
local res = world:resource(res_id)

print(res.value)
```

## Solution

1. add a `ReflectFromPtr` type with a `FromType<T: Reflect>` implementation and the following methods:
- `     pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect`
- `     pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect`

Safety requirements of the methods are that you need to check that the `ReflectFromPtr` was constructed for the correct type.

2. add that type to the `TypeRegistration` in the `GetTypeRegistration` impl generated by `#[derive(Reflect)]`.
This is different to other reflected traits because it doesn't need `#[reflect(ReflectReflectFromPtr)]` which IMO should be there by default.

Co-authored-by: Jakob Hellermann <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
@bors
Copy link
Contributor

bors bot commented Jul 19, 2022

Canceled.

@cart
Copy link
Member

cart commented Jul 19, 2022

bors retry

bors bot pushed a commit that referenced this pull request Jul 19, 2022
…t ()` (#4475)

# Objective

#4447 adds functions that can fetch resources/components as `*const ()` ptr by providing the `ComponentId`. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a `&dyn Reflect` which is the pointer + a pointer to the VTable of the `Reflect` impl.

By adding a `ReflectFromPtr` type that is included in the type type registration when deriving `Reflect`, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection:

```rust
#[derive(Reflect)]
struct StringResource {
    value: String
}
```

```lua
local res_id = world:resource_id_by_name("example::StringResource")
local res = world:resource(res_id)

print(res.value)
```

## Solution

1. add a `ReflectFromPtr` type with a `FromType<T: Reflect>` implementation and the following methods:
- `     pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect`
- `     pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect`

Safety requirements of the methods are that you need to check that the `ReflectFromPtr` was constructed for the correct type.

2. add that type to the `TypeRegistration` in the `GetTypeRegistration` impl generated by `#[derive(Reflect)]`.
This is different to other reflected traits because it doesn't need `#[reflect(ReflectReflectFromPtr)]` which IMO should be there by default.

Co-authored-by: Jakob Hellermann <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
@bors bors bot changed the title bevy_reflect: ReflectFromPtr to create &dyn Reflect from a *const () [Merged by Bors] - bevy_reflect: ReflectFromPtr to create &dyn Reflect from a *const () Jul 19, 2022
@bors bors bot closed this Jul 19, 2022
@jakobhellermann jakobhellermann deleted the bevy-reflect-from-ptr branch July 20, 2022 07:28
inodentry pushed a commit to IyesGames/bevy that referenced this pull request Aug 8, 2022
…t ()` (bevyengine#4475)

# Objective

bevyengine#4447 adds functions that can fetch resources/components as `*const ()` ptr by providing the `ComponentId`. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a `&dyn Reflect` which is the pointer + a pointer to the VTable of the `Reflect` impl.

By adding a `ReflectFromPtr` type that is included in the type type registration when deriving `Reflect`, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection:

```rust
#[derive(Reflect)]
struct StringResource {
    value: String
}
```

```lua
local res_id = world:resource_id_by_name("example::StringResource")
local res = world:resource(res_id)

print(res.value)
```

## Solution

1. add a `ReflectFromPtr` type with a `FromType<T: Reflect>` implementation and the following methods:
- `     pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect`
- `     pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect`

Safety requirements of the methods are that you need to check that the `ReflectFromPtr` was constructed for the correct type.

2. add that type to the `TypeRegistration` in the `GetTypeRegistration` impl generated by `#[derive(Reflect)]`.
This is different to other reflected traits because it doesn't need `#[reflect(ReflectReflectFromPtr)]` which IMO should be there by default.

Co-authored-by: Jakob Hellermann <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
james7132 pushed a commit to james7132/bevy that referenced this pull request Oct 28, 2022
…t ()` (bevyengine#4475)

# Objective

bevyengine#4447 adds functions that can fetch resources/components as `*const ()` ptr by providing the `ComponentId`. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a `&dyn Reflect` which is the pointer + a pointer to the VTable of the `Reflect` impl.

By adding a `ReflectFromPtr` type that is included in the type type registration when deriving `Reflect`, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection:

```rust
#[derive(Reflect)]
struct StringResource {
    value: String
}
```

```lua
local res_id = world:resource_id_by_name("example::StringResource")
local res = world:resource(res_id)

print(res.value)
```

## Solution

1. add a `ReflectFromPtr` type with a `FromType<T: Reflect>` implementation and the following methods:
- `     pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect`
- `     pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect`

Safety requirements of the methods are that you need to check that the `ReflectFromPtr` was constructed for the correct type.

2. add that type to the `TypeRegistration` in the `GetTypeRegistration` impl generated by `#[derive(Reflect)]`.
This is different to other reflected traits because it doesn't need `#[reflect(ReflectReflectFromPtr)]` which IMO should be there by default.

Co-authored-by: Jakob Hellermann <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…t ()` (bevyengine#4475)

# Objective

bevyengine#4447 adds functions that can fetch resources/components as `*const ()` ptr by providing the `ComponentId`. This alone is not enough for them to be usable safely with reflection, because there is no general way to go from the raw pointer to a `&dyn Reflect` which is the pointer + a pointer to the VTable of the `Reflect` impl.

By adding a `ReflectFromPtr` type that is included in the type type registration when deriving `Reflect`, safe functions can be implemented in scripting languages that don't assume a type layout and can access the component data via reflection:

```rust
#[derive(Reflect)]
struct StringResource {
    value: String
}
```

```lua
local res_id = world:resource_id_by_name("example::StringResource")
local res = world:resource(res_id)

print(res.value)
```

## Solution

1. add a `ReflectFromPtr` type with a `FromType<T: Reflect>` implementation and the following methods:
- `     pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect`
- `     pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mud dyn Reflect`

Safety requirements of the methods are that you need to check that the `ReflectFromPtr` was constructed for the correct type.

2. add that type to the `TypeRegistration` in the `GetTypeRegistration` impl generated by `#[derive(Reflect)]`.
This is different to other reflected traits because it doesn't need `#[reflect(ReflectReflectFromPtr)]` which IMO should be there by default.

Co-authored-by: Jakob Hellermann <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

The pointer types introduced in bevyengine#3001 are useful not just in `bevy_ecs`, but also in crates like `bevy_reflect` (bevyengine#4475) or even outside of bevy.

## Solution

Extract `Ptr<'a>`, `PtrMut<'a>`, `OwnedPtr<'a>`, `ThinSlicePtr<'a, T>` and `UnsafeCellDeref` from `bevy_ecs::ptr` into `bevy_ptr`.

**Note:** `bevy_ecs` still reexports the `bevy_ptr` as `bevy_ecs::ptr` so that crates like `bevy_transform` can use the `Bundle` derive without needing to depend on `bevy_ptr` themselves.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Modding Supporting infrastructure for player-controlled modifications to games A-Reflection Runtime information about types C-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Controversial There is active debate or serious implications around merging this PR
Projects
Archived in project
Archived in project
Development

Successfully merging this pull request may close these issues.

7 participants