Skip to content

Commit 4f49680

Browse files
authored
Ensure systems passed to Combine cannot be called re-entrantly either (#20689)
# Objective - Fix #14709 ## Solution - Add an extra parameter to `Combine::combine` of a generic type `T`, which ensures only one of the two closures can be called at any given time, including re-entrantly.
1 parent afc721e commit 4f49680

File tree

3 files changed

+56
-37
lines changed

3 files changed

+56
-37
lines changed

crates/bevy_ecs/src/schedule/condition.rs

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,12 +1147,13 @@ where
11471147
type In = In;
11481148
type Out = bool;
11491149

1150-
fn combine(
1150+
fn combine<T>(
11511151
input: <Self::In as SystemInput>::Inner<'_>,
1152-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
1153-
b: impl FnOnce(SystemIn<'_, A>) -> Result<B::Out, RunSystemError>,
1152+
data: &mut T,
1153+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
1154+
b: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<B::Out, RunSystemError>,
11541155
) -> Result<Self::Out, RunSystemError> {
1155-
Ok(a(input)? && b(input)?)
1156+
Ok(a(input, data)? && b(input, data)?)
11561157
}
11571158
}
11581159

@@ -1168,12 +1169,13 @@ where
11681169
type In = In;
11691170
type Out = bool;
11701171

1171-
fn combine(
1172+
fn combine<T>(
11721173
input: <Self::In as SystemInput>::Inner<'_>,
1173-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
1174-
b: impl FnOnce(SystemIn<'_, A>) -> Result<B::Out, RunSystemError>,
1174+
data: &mut T,
1175+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
1176+
b: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<B::Out, RunSystemError>,
11751177
) -> Result<Self::Out, RunSystemError> {
1176-
Ok(!(a(input)? && b(input)?))
1178+
Ok(!(a(input, data)? && b(input, data)?))
11771179
}
11781180
}
11791181

@@ -1189,12 +1191,13 @@ where
11891191
type In = In;
11901192
type Out = bool;
11911193

1192-
fn combine(
1194+
fn combine<T>(
11931195
input: <Self::In as SystemInput>::Inner<'_>,
1194-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
1195-
b: impl FnOnce(SystemIn<'_, A>) -> Result<B::Out, RunSystemError>,
1196+
data: &mut T,
1197+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
1198+
b: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<B::Out, RunSystemError>,
11961199
) -> Result<Self::Out, RunSystemError> {
1197-
Ok(!(a(input)? || b(input)?))
1200+
Ok(!(a(input, data)? || b(input, data)?))
11981201
}
11991202
}
12001203

@@ -1210,12 +1213,13 @@ where
12101213
type In = In;
12111214
type Out = bool;
12121215

1213-
fn combine(
1216+
fn combine<T>(
12141217
input: <Self::In as SystemInput>::Inner<'_>,
1215-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
1216-
b: impl FnOnce(SystemIn<'_, A>) -> Result<B::Out, RunSystemError>,
1218+
data: &mut T,
1219+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
1220+
b: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<B::Out, RunSystemError>,
12171221
) -> Result<Self::Out, RunSystemError> {
1218-
Ok(a(input)? || b(input)?)
1222+
Ok(a(input, data)? || b(input, data)?)
12191223
}
12201224
}
12211225

@@ -1231,12 +1235,13 @@ where
12311235
type In = In;
12321236
type Out = bool;
12331237

1234-
fn combine(
1238+
fn combine<T>(
12351239
input: <Self::In as SystemInput>::Inner<'_>,
1236-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
1237-
b: impl FnOnce(SystemIn<'_, A>) -> Result<B::Out, RunSystemError>,
1240+
data: &mut T,
1241+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
1242+
b: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<B::Out, RunSystemError>,
12381243
) -> Result<Self::Out, RunSystemError> {
1239-
Ok(!(a(input)? ^ b(input)?))
1244+
Ok(!(a(input, data)? ^ b(input, data)?))
12401245
}
12411246
}
12421247

@@ -1252,12 +1257,13 @@ where
12521257
type In = In;
12531258
type Out = bool;
12541259

1255-
fn combine(
1260+
fn combine<T>(
12561261
input: <Self::In as SystemInput>::Inner<'_>,
1257-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
1258-
b: impl FnOnce(SystemIn<'_, A>) -> Result<B::Out, RunSystemError>,
1262+
data: &mut T,
1263+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
1264+
b: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<B::Out, RunSystemError>,
12591265
) -> Result<Self::Out, RunSystemError> {
1260-
Ok(a(input)? ^ b(input)?)
1266+
Ok(a(input, data)? ^ b(input, data)?)
12611267
}
12621268
}
12631269

crates/bevy_ecs/src/system/combinator.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ use super::{IntoSystem, ReadOnlySystem, RunSystemError, System};
3636
/// type In = ();
3737
/// type Out = bool;
3838
///
39-
/// fn combine(
39+
/// fn combine<T>(
4040
/// _input: Self::In,
41-
/// a: impl FnOnce(A::In) -> Result<A::Out, RunSystemError>,
42-
/// b: impl FnOnce(B::In) -> Result<B::Out, RunSystemError>,
41+
/// data: &mut T,
42+
/// a: impl FnOnce(A::In, &mut T) -> Result<A::Out, RunSystemError>,
43+
/// b: impl FnOnce(B::In, &mut T) -> Result<B::Out, RunSystemError>,
4344
/// ) -> Result<Self::Out, RunSystemError> {
44-
/// Ok(a(())? ^ b(())?)
45+
/// Ok(a((), data)? ^ b((), data)?)
4546
/// }
4647
/// }
4748
///
@@ -99,10 +100,11 @@ pub trait Combine<A: System, B: System> {
99100
/// the two composite systems are invoked and their outputs are combined.
100101
///
101102
/// See the trait-level docs for [`Combine`] for an example implementation.
102-
fn combine(
103+
fn combine<T>(
103104
input: <Self::In as SystemInput>::Inner<'_>,
104-
a: impl FnOnce(SystemIn<'_, A>) -> Result<A::Out, RunSystemError>,
105-
b: impl FnOnce(SystemIn<'_, B>) -> Result<B::Out, RunSystemError>,
105+
data: &mut T,
106+
a: impl FnOnce(SystemIn<'_, A>, &mut T) -> Result<A::Out, RunSystemError>,
107+
b: impl FnOnce(SystemIn<'_, B>, &mut T) -> Result<B::Out, RunSystemError>,
106108
) -> Result<Self::Out, RunSystemError>;
107109
}
108110

@@ -153,20 +155,25 @@ where
153155
input: SystemIn<'_, Self>,
154156
world: UnsafeWorldCell,
155157
) -> Result<Self::Out, RunSystemError> {
158+
struct PrivateUnsafeWorldCell<'w>(UnsafeWorldCell<'w>);
159+
156160
Func::combine(
157161
input,
162+
&mut PrivateUnsafeWorldCell(world),
158163
// SAFETY: The world accesses for both underlying systems have been registered,
159164
// so the caller will guarantee that no other systems will conflict with `a` or `b`.
160165
// If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.
161-
// Since these closures are `!Send + !Sync + !'static`, they can never be called
162-
// in parallel, so their world accesses will not conflict with each other.
163-
|input| unsafe { self.a.run_unsafe(input, world) },
166+
// Since we require a `combine` to pass in a mutable reference to `world` and that's a private type
167+
// passed to a function as an unbound non-'static generic argument, they can never be called in parallel
168+
// or re-entrantly because that would require forging another instance of `PrivateUnsafeWorldCell`.
169+
// This means that the world accesses in the two closures will not conflict with each other.
170+
|input, world| unsafe { self.a.run_unsafe(input, world.0) },
164171
// `Self::validate_param_unsafe` already validated the first system,
165172
// but we still need to validate the second system once the first one runs.
166173
// SAFETY: See the comment above.
167-
|input| unsafe {
168-
self.b.validate_param_unsafe(world)?;
169-
self.b.run_unsafe(input, world)
174+
|input, world| unsafe {
175+
self.b.validate_param_unsafe(world.0)?;
176+
self.b.run_unsafe(input, world.0)
170177
},
171178
)
172179
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Combine now takes an extra parameter
3+
pull_requests: [20689]
4+
---
5+
6+
The `Combine::combine` method now takes an extra parameter that needs to be passed mutably to the two given closures. This allows fixing a soundness issue which manifested when the two closures were called re-entrantly.

0 commit comments

Comments
 (0)