-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Bevy version
From Cargo.lock
[[package]]
name = "bevy"
version = "0.3.0"
source = "git+https://github.com/bevyengine/bevy.git#3d386a77b406ee65d6965ad91b29ea3acb8bcf3a"
dependencies = [
"bevy_internal",
]
Operating system & version
Windows 10 but I guess it is not relevant
What you did
This sample program. Notice 'static lifetime of Res (ResMut or Query works too, I believe).
use std::sync::{Mutex, MutexGuard};
use bevy::prelude::*;
use once_cell::sync::OnceCell;
static INSTANCE: OnceCell<Mutex<Option<Res<'static, String>>>> = OnceCell::new();
fn main() {
INSTANCE.get_or_init(||Mutex::new(None));
App::build()
.add_resource("HelloWorld".to_string())
.add_system(ub_system.system())
.run();
let guard: MutexGuard<Option<Res<'static, String>>> = INSTANCE.get().expect("uninited?").lock().expect("Failed to acquire lock");
let resource: &Res<'static, String> = guard.as_ref().unwrap();
println!("Wow, I read freed memory with such data: {:?}", (**resource).as_bytes());
}
fn ub_system(string: Res<'static, String>){
let mut guard = INSTANCE.get().expect("uninited?").lock().expect("Failed to acquire lock");
*guard = Some(string);
}What you expected to happen
This code should not compile because this is UB.
What actually happened
In my case it prints some garbage.
Wow, I read freed memory with such data: [0, 223, 3, 195, 196, 1, 0, 0, 176, 169]
PS D:\UserFolder\poc_bevy_unsound> cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target\debug\poc_bevy_unsound.exe`
Wow, I read freed memory with such data: [224, 84, 1, 115, 128, 1, 0, 0, 32, 151]
PS D:\UserFolder\poc_bevy_unsound> cargo run --release
Finished release [optimized] target(s) in 0.29s
Running `target\release\poc_bevy_unsound.exe`
Wow, I read freed memory with such data: [112, 58, 118, 247, 229, 1, 0, 0, 112, 132]
PS D:\UserFolder\poc_bevy_unsound> cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target\debug\poc_bevy_unsound.exe`
Wow, I read freed memory with such data: [144, 58, 46, 167, 239, 1, 0, 0, 48, 97]
Additional information
I recently tried to implement own ECS system for my game and bevy was my inspiration. I implement it mostly for fun and I found same bug in my ECS during implementation.
We discussed it on Reddit and I found a solution.
Look at this playground link.
So I suggest:
- Add lifetime parameter to the IntoSystem trait Let assume it named as
'app. - Use this lifetime in implementation of system for functions and get_param method. Add
'appconstraints to requested types for function. - Make
systemmethod in IntoSystem trait unsafe (because it is). - Change
add_systemmethod of App to accept IntoSystem trait implementation and use it like
fn my_system(v: Res<T>){}
App::build().add_system(my_system)And implement it like:
fn add_system<'a, T>(&'a mut self, sys: T) where T: 'static + IntoSystem<'a>{
let system = unsafe{ sys.system() };
// old code goes here
}This would prevent passing function that accepts Res<'static, T> into App unless it is 'static itself but mutation of 'static variable available only in unsafe code so it is fine.
Note: Code example above doesn't compile on Bevy 0.3.0 from crates.io.