Skip to content

Component::register_required_components can be implemented in an unsound way #20173

@SkiFire13

Description

@SkiFire13

Bevy version

0.16 and commit f964ee1

What you did

I implemented Component::register_required_components by using a different ComponentsRegistrator instance than the one provided when registering required components in the given RequiredComponents.

use bevy_ecs::prelude::*;

struct A;

impl Component for A {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::Table;

    type Mutability = bevy_ecs::component::Mutable;

    fn register_required_components(
        _component_id: bevy_ecs::component::ComponentId,
        components: &mut bevy_ecs::component::ComponentsRegistrator,
        required_components: &mut bevy_ecs::component::RequiredComponents,
        _inheritance_depth: u16,
        _recursion_check_stack: &mut Vec<bevy_ecs::component::ComponentId>,
    ) {
        #[derive(Component)]
        struct AFake;

        #[derive(Component)]
        struct BFake(usize);

        components.register_component::<B>();

        let mut world = World::new();
        let mut wrong_components = world.components_registrator();
        wrong_components.register_component::<AFake>();

        required_components.register::<BFake>(&mut wrong_components, || BFake(0x1), 0);
    }
}

#[derive(Component)]
struct B(&'static u8);

let mut world = World::new();
let e = world.spawn(A).get::<B>().unwrap().0;
println!("{e}"); // will try to dereference the address 0x1

What went wrong

The code was accepted and at runtime a component is initialized with the wrong type, leading to UB.

Additional informations

To be sound RequiredComponents should require all required components to be registered in the Components/ComponentsRegistrator instance associated with it. This is however broken by the fact that we expect safe code to pass the correct instance of ComponentsRegistrator to RequiredComponents. This could be solved by replacing them with a struct that wraps both of them and ensures the methods on RequiredComponents are called with the right ComponentsRegistrator.

Found while implementing #20110

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-BugAn unexpected or incorrect behaviorP-UnsoundA bug that results in undefined compiler behaviorS-Needs-ReviewNeeds reviewer attention (from anyone!) to move forward

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions