Skip to content

Commit 3b0ca37

Browse files
#1611 Fix race condition in enum reflection code
1 parent c8c202e commit 3b0ca37

File tree

7 files changed

+294
-188
lines changed

7 files changed

+294
-188
lines changed

distr/flecs.c

+4
Original file line numberDiff line numberDiff line change
@@ -21562,6 +21562,10 @@ ecs_entity_t ecs_cpp_enum_constant_register(
2156221562
ecs_suspend_readonly_state_t readonly_state;
2156321563
world = flecs_suspend_readonly(world, &readonly_state);
2156421564

21565+
ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL);
21566+
ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL);
21567+
ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL);
21568+
2156521569
const char *parent_name = ecs_get_name(world, parent);
2156621570
ecs_size_t parent_name_len = ecs_os_strlen(parent_name);
2156721571
if (!ecs_os_strncmp(name, parent_name, parent_name_len)) {

distr/flecs.h

+121-93
Original file line numberDiff line numberDiff line change
@@ -18225,26 +18225,20 @@ struct enum_is_valid {
1822518225
/** Extract name of constant from string */
1822618226
template <typename E, E C>
1822718227
static const char* enum_constant_to_name() {
18228-
static const size_t len = ECS_FUNC_TYPE_LEN(const char*, enum_constant_to_name, ECS_FUNC_NAME);
18228+
static const size_t len = ECS_FUNC_TYPE_LEN(
18229+
const char*, enum_constant_to_name, ECS_FUNC_NAME);
1822918230
static char result[len + 1] = {};
1823018231
return ecs_cpp_get_constant_name(
1823118232
result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME),
1823218233
ECS_FUNC_NAME_BACK);
1823318234
}
1823418235

18235-
/** Enumeration constant data */
18236-
template<typename T>
18237-
struct enum_constant_data {
18238-
int32_t index; // Global index used to obtain world local entity id
18239-
T offset;
18240-
};
18241-
1824218236
/**
1824318237
* @brief Provides utilities for enum reflection.
1824418238
*
18245-
* This struct provides static functions for enum reflection, including conversion
18246-
* between enum values and their underlying integral types, and iteration over enum
18247-
* values.
18239+
* This struct provides static functions for enum reflection, including
18240+
* conversion between enum values and their underlying integral types, and
18241+
* iteration over enum values.
1824818242
*
1824918243
* @tparam E The enum type.
1825018244
* @tparam Handler The handler for enum reflection operations.
@@ -18254,11 +18248,13 @@ struct enum_reflection {
1825418248
using U = underlying_type_t<E>;
1825518249

1825618250
/**
18257-
* @brief Iterates over the range [Low, High] of enum values between Low and High.
18251+
* @brief Iterates over the range [Low, High] of enum values between Low and
18252+
* High.
1825818253
*
18259-
* Recursively divide and conquers the search space to reduce the template-depth. Once
18260-
* recursive division is complete, calls Handle<E>::handle_constant in ascending order,
18261-
* passing the values computed up the chain.
18254+
* Recursively divide and conquers the search space to reduce the
18255+
* template-depth. Once recursive division is complete, calls
18256+
* Handle<E>::handle_constant in ascending order, passing the values
18257+
* computed up the chain.
1826218258
*
1826318259
* @tparam Low The lower bound of the search range, inclusive.
1826418260
* @tparam High The upper bound of the search range, inclusive.
@@ -18268,23 +18264,27 @@ struct enum_reflection {
1826818264
* @return constexpr U The result of the iteration.
1826918265
*/
1827018266
template <U Low, U High, typename... Args>
18271-
static constexpr U each_enum_range(U last_value, Args... args) {
18267+
static constexpr U each_enum_range(U last_value, Args&... args) {
1827218268
return High - Low <= 1
1827318269
? High == Low
1827418270
? Handler::template handle_constant<Low>(last_value, args...)
18275-
: Handler::template handle_constant<High>(Handler::template handle_constant<Low>(last_value, args...), args...)
18271+
: Handler::template handle_constant<High>(
18272+
Handler::template handle_constant<Low>(last_value, args...),
18273+
args...)
1827618274
: each_enum_range<(Low + High) / 2 + 1, High>(
1827718275
each_enum_range<Low, (Low + High) / 2>(last_value, args...),
1827818276
args...
1827918277
);
1828018278
}
1828118279

1828218280
/**
18283-
* @brief Iterates over the mask range (Low, High] of enum values between Low and High.
18281+
* @brief Iterates over the mask range (Low, High] of enum values between
18282+
* Low and High.
1828418283
*
18285-
* Recursively iterates the search space, looking for enums defined as multiple-of-2
18286-
* bitmasks. Each iteration, shifts bit to the right until it hits Low, then calls
18287-
* Handler::handle_constant for each bitmask in ascending order.
18284+
* Recursively iterates the search space, looking for enums defined as
18285+
* multiple-of-2 bitmasks. Each iteration, shifts bit to the right until it
18286+
* hits Low, then calls Handler::handle_constant for each bitmask in
18287+
* ascending order.
1828818288
*
1828918289
* @tparam Low The lower bound of the search range, not inclusive
1829018290
* @tparam High The upper bound of the search range, inclusive.
@@ -18294,8 +18294,9 @@ struct enum_reflection {
1829418294
* @return constexpr U The result of the iteration.
1829518295
*/
1829618296
template <U Low, U High, typename... Args>
18297-
static constexpr U each_mask_range(U last_value, Args... args) {
18298-
// If Low shares any bits with Current Flag, or if High is less than/equal to Low (and High isn't negative because max-flag signed)
18297+
static constexpr U each_mask_range(U last_value, Args&... args) {
18298+
// If Low shares any bits with Current Flag, or if High is less
18299+
// than/equal to Low (and High isn't negative because max-flag signed)
1829918300
return (Low & High) || (High <= Low && High != high_bit)
1830018301
? last_value
1830118302
: Handler::template handle_constant<High>(
@@ -18317,105 +18318,116 @@ struct enum_reflection {
1831718318
* @return constexpr U The result of the iteration.
1831818319
*/
1831918320
template <U Value = static_cast<U>(FLECS_ENUM_MAX(E)), typename... Args>
18320-
static constexpr U each_enum(Args... args) {
18321-
return each_mask_range<Value, high_bit>(each_enum_range<0, Value>(0, args...), args...);
18321+
static constexpr U each_enum(Args&... args) {
18322+
return each_mask_range<Value, high_bit>(
18323+
each_enum_range<0, Value>(0, args...), args...);
1832218324
}
1832318325
/* to avoid warnings with bit manipulation, calculate the high bit with an
1832418326
unsigned type of the same size: */
1832518327
using UU = typename std::make_unsigned<U>::type;
18326-
static const U high_bit = static_cast<U>(static_cast<UU>(1) << (sizeof(UU) * 8 - 1));
18328+
static const U high_bit =
18329+
static_cast<U>(static_cast<UU>(1) << (sizeof(UU) * 8 - 1));
18330+
};
18331+
18332+
/** Enumeration constant data */
18333+
template<typename T>
18334+
struct enum_constant {
18335+
int32_t index; // Global index used to obtain world local entity id
18336+
T value;
18337+
T offset;
18338+
const char *name;
1832718339
};
1832818340

18329-
/** Enumeration type data */
18330-
template<typename E>
18331-
struct enum_data_impl {
18341+
/** Class that scans an enum for constants, extracts names & creates entities */
18342+
template <typename E>
18343+
struct enum_type {
1833218344
private:
18345+
using This = enum_type<E>;
1833318346
using U = underlying_type_t<E>;
1833418347

1833518348
/**
1833618349
* @brief Handler struct for generating compile-time count of enum constants.
1833718350
*/
1833818351
struct reflection_count {
18339-
template <U Value, flecs::if_not_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
18352+
template <U Value,
18353+
flecs::if_not_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
1834018354
static constexpr U handle_constant(U last_value) {
1834118355
return last_value;
1834218356
}
1834318357

18344-
template <U Value, flecs::if_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
18358+
template <U Value,
18359+
flecs::if_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
1834518360
static constexpr U handle_constant(U last_value) {
1834618361
return 1 + last_value;
1834718362
}
1834818363
};
1834918364

18350-
public:
18351-
int min;
18352-
int max;
18353-
bool has_contiguous;
18354-
// If enum constants start not-sparse, contiguous_until will be the index of the first sparse value, or end of the constants array
18355-
U contiguous_until;
18356-
// Compile-time generated count of enum constants.
18357-
static constexpr unsigned int constants_size = enum_reflection<E, reflection_count>::template each_enum< static_cast<U>(enum_last<E>::value) >();
18358-
// Constants array is sized to the number of found-constants, or 1 (to avoid 0-sized array)
18359-
enum_constant_data<U> constants[constants_size? constants_size: 1];
18360-
};
18361-
18362-
/** Class that scans an enum for constants, extracts names & creates entities */
18363-
template <typename E>
18364-
struct enum_type {
18365-
private:
18366-
using U = underlying_type_t<E>;
18367-
1836818365
/**
18369-
* @brief Helper struct for filling enum_type's static `enum_data_impl<E>` member with reflection data.
18366+
* @brief Helper struct for filling enum_type's static `enum_data_impl<E>`
18367+
* member with reflection data.
1837018368
*
18371-
* Because reflection occurs in-order, we can use current value/last value to determine continuity, and
18372-
* use that as a lookup heuristic later on.
18369+
* Because reflection occurs in-order, we can use current value/last value
18370+
* to determine continuity, and use that as a lookup heuristic later on.
1837318371
*/
1837418372
struct reflection_init {
18375-
template <U Value, flecs::if_not_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
18376-
static U handle_constant(U last_value, flecs::world_t*) {
18373+
template <U Value,
18374+
flecs::if_not_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
18375+
static U handle_constant(U last_value, This&) {
1837718376
// Search for constant failed. Pass last valid value through.
1837818377
return last_value;
1837918378
}
1838018379

18381-
template <U Value, flecs::if_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
18382-
static U handle_constant(U last_value, flecs::world_t *world) {
18380+
template <U Value,
18381+
flecs::if_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
18382+
static U handle_constant(U last_value, This& me) {
1838318383
// Constant is valid, so fill reflection data.
1838418384
auto v = Value;
1838518385
const char *name = enum_constant_to_name<E, flecs_enum_cast(E, Value)>();
1838618386

18387-
++enum_type<E>::data.max; // Increment cursor as we build constants array.
18387+
++me.max; // Increment cursor as we build constants array.
1838818388

18389-
// If the enum was previously contiguous, and continues to be through the current value...
18390-
if (enum_type<E>::data.has_contiguous && static_cast<U>(enum_type<E>::data.max) == v && enum_type<E>::data.contiguous_until == v) {
18391-
++enum_type<E>::data.contiguous_until;
18389+
// If the enum was previously contiguous, and continues to be
18390+
// through the current value...
18391+
if (me.has_contiguous && static_cast<U>(me.max) == v && me.contiguous_until == v) {
18392+
++me.contiguous_until;
1839218393
}
18393-
// else, if the enum was never contiguous and hasn't been set as not contiguous...
18394-
else if (!enum_type<E>::data.contiguous_until && enum_type<E>::data.has_contiguous) {
18395-
enum_type<E>::data.has_contiguous = false;
18394+
18395+
// else, if the enum was never contiguous and hasn't been set as not
18396+
// contiguous...
18397+
else if (!me.contiguous_until && me.has_contiguous) {
18398+
me.has_contiguous = false;
1839618399
}
1839718400

18398-
ecs_assert(!(last_value > 0 && v < std::numeric_limits<U>::min() + last_value), ECS_UNSUPPORTED,
18399-
"Signed integer enums causes integer overflow when recording offset from high positive to"
18400-
" low negative. Consider using unsigned integers as underlying type.");
18401-
enum_type<E>::data.constants[enum_type<E>::data.max].offset = v - last_value;
18402-
if (!enum_type<E>::data.constants[enum_type<E>::data.max].index) {
18403-
enum_type<E>::data.constants[enum_type<E>::data.max].index =
18401+
ecs_assert(!(last_value > 0 &&
18402+
v < std::numeric_limits<U>::min() + last_value),
18403+
ECS_UNSUPPORTED,
18404+
"Signed integer enums causes integer overflow when recording "
18405+
"offset from high positive to low negative. Consider using "
18406+
"unsigned integers as underlying type.");
18407+
18408+
me.constants[me.max].value = v;
18409+
me.constants[me.max].offset = v - last_value;
18410+
me.constants[me.max].name = name;
18411+
if (!me.constants[me.max].index) {
18412+
me.constants[me.max].index =
1840418413
flecs_component_ids_index_get();
1840518414
}
18406-
18407-
flecs::entity_t constant = ecs_cpp_enum_constant_register(
18408-
world, type<E>::id(world), 0, name, &v, type<U>::id(world), sizeof(U));
18409-
flecs_component_ids_set(world,
18410-
enum_type<E>::data.constants[enum_type<E>::data.max].index,
18411-
constant);
1841218415

1841318416
return v;
1841418417
}
1841518418
};
1841618419
public:
1841718420

18418-
static enum_data_impl<E> data;
18421+
enum_type() {
18422+
// Initialize/reset reflection data values to default state.
18423+
min = 0;
18424+
max = -1;
18425+
has_contiguous = true;
18426+
contiguous_until = 0;
18427+
18428+
enum_reflection<E, reflection_init>::
18429+
template each_enum< static_cast<U>(enum_last<E>::value) >(*this);
18430+
}
1841918431

1842018432
static enum_type<E>& get() {
1842118433
static _::enum_type<E> instance;
@@ -18425,37 +18437,53 @@ struct enum_type {
1842518437
flecs::entity_t entity(E value) const {
1842618438
int index = index_by_value(value);
1842718439
if (index >= 0) {
18428-
return data.constants[index].id;
18440+
return constants[index].id;
1842918441
}
1843018442
return 0;
1843118443
}
1843218444

18433-
void init(flecs::world_t *world, flecs::entity_t id) {
18445+
void register_for_world(flecs::world_t *world, flecs::entity_t id) {
1843418446
#if !FLECS_CPP_ENUM_REFLECTION_SUPPORT
1843518447
ecs_abort(ECS_UNSUPPORTED, "enum reflection requires gcc 7.5 or higher")
1843618448
#endif
18437-
// Initialize/reset reflection data values to default state.
18438-
data.min = 0;
18439-
data.max = -1;
18440-
data.has_contiguous = true;
18441-
data.contiguous_until = 0;
1844218449

1844318450
ecs_log_push();
1844418451
ecs_cpp_enum_init(world, id, type<U>::id(world));
18445-
// data.id = id;
1844618452

18447-
// Generate reflection data
18448-
enum_reflection<E, reflection_init>::template each_enum< static_cast<U>(enum_last<E>::value) >(world);
18453+
for (U v = 0; v < static_cast<U>(max + 1); v ++) {
18454+
if (constants[v].index) {
18455+
flecs::entity_t constant = ecs_cpp_enum_constant_register(world,
18456+
type<E>::id(world), 0, constants[v].name, &constants[v].value,
18457+
type<U>::id(world), sizeof(U));
18458+
18459+
flecs_component_ids_set(world, constants[v].index, constant);
18460+
}
18461+
}
18462+
1844918463
ecs_log_pop();
1845018464
}
18451-
};
1845218465

18453-
template <typename E>
18454-
enum_data_impl<E> enum_type<E>::data;
18466+
int min;
18467+
int max;
18468+
bool has_contiguous;
18469+
18470+
// If enum constants start not-sparse, contiguous_until will be the index of
18471+
// the first sparse value, or end of the constants array
18472+
U contiguous_until;
18473+
18474+
// Compile-time generated count of enum constants.
18475+
static constexpr unsigned int constants_size =
18476+
enum_reflection<E, reflection_count>::
18477+
template each_enum< static_cast<U>(enum_last<E>::value) >();
18478+
18479+
// Constants array is sized to the number of found-constants, or 1
18480+
// to avoid 0-sized array
18481+
enum_constant<U> constants[constants_size? constants_size: 1] = {};
18482+
};
1845518483

1845618484
template <typename E, if_t< is_enum<E>::value > = 0>
1845718485
inline static void init_enum(flecs::world_t *world, flecs::entity_t id) {
18458-
_::enum_type<E>::get().init(world, id);
18486+
_::enum_type<E>::get().register_for_world(world, id);
1845918487
}
1846018488

1846118489
template <typename E, if_not_t< is_enum<E>::value > = 0>
@@ -18468,7 +18496,7 @@ template <typename E>
1846818496
struct enum_data {
1846918497
using U = underlying_type_t<E>;
1847018498

18471-
enum_data(flecs::world_t *world, _::enum_data_impl<E>& impl)
18499+
enum_data(flecs::world_t *world, _::enum_type<E>& impl)
1847218500
: world_(world)
1847318501
, impl_(impl) { }
1847418502

@@ -18549,15 +18577,15 @@ struct enum_data {
1854918577
flecs::entity entity(E value) const;
1855018578

1855118579
flecs::world_t *world_;
18552-
_::enum_data_impl<E>& impl_;
18580+
_::enum_type<E>& impl_;
1855318581
};
1855418582

1855518583
/** Convenience function for getting enum reflection data */
1855618584
template <typename E>
1855718585
enum_data<E> enum_type(flecs::world_t *world) {
1855818586
_::type<E>::id(world); // Ensure enum is registered
1855918587
auto& ref = _::enum_type<E>::get();
18560-
return enum_data<E>(world, ref.data);
18588+
return enum_data<E>(world, ref);
1856118589
}
1856218590

1856318591
} // namespace flecs

0 commit comments

Comments
 (0)