Skip to content

Commit 9fc68d4

Browse files
fix: race condition in EnumWrappers#initialize (#3121)
Adds synchronization primitives inside the initialization function to ensure that when the function returns, everything is guaranteed to be initialized. Fixes #3120
1 parent f227b17 commit 9fc68d4

File tree

1 file changed

+112
-101
lines changed

1 file changed

+112
-101
lines changed

src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java

Lines changed: 112 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ public enum ClientIntent {
664664
private static Class<?> CHAT_FORMATTING_CLASS = null;
665665
private static Class<?> CLIENT_INTENT_CLASS = null;
666666

667+
private static boolean INITIALIZING = false;
667668
private static boolean INITIALIZED = false;
668669
private static Map<Class<?>, EquivalentConverter<?>> FROM_NATIVE = new HashMap<>();
669670
private static Map<Class<?>, EquivalentConverter<?>> FROM_WRAPPER = new HashMap<>();
@@ -676,119 +677,129 @@ private static void initialize() {
676677
if (INITIALIZED)
677678
return;
678679

679-
INITIALIZED = true;
680+
synchronized (EnumWrappers.class) {
681+
// Recheck initialization status inside the lock
682+
if (INITIALIZING || INITIALIZED)
683+
return;
680684

681-
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
682-
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
685+
// Prevent circular calls to initialize during initialization
686+
// (certain methods below indirectly call initialize again)
687+
INITIALIZING = true;
683688

684-
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
685-
CHAT_VISIBILITY_CLASS = MinecraftReflection.getMinecraftClass("world.entity.player.EnumChatVisibility", "world.entity.player.ChatVisibility", "world.entity.player.ChatVisiblity"); // Some versions have a typo
686-
} else {
687-
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
688-
}
689+
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
690+
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
689691

690-
try {
691-
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
692-
} catch (Exception ex) {
693-
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
694-
}
692+
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
693+
CHAT_VISIBILITY_CLASS = MinecraftReflection.getMinecraftClass("world.entity.player.EnumChatVisibility", "world.entity.player.ChatVisibility", "world.entity.player.ChatVisiblity"); // Some versions have a typo
694+
} else {
695+
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
696+
}
695697

696-
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
697-
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
698-
} else {
699-
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
700-
}
698+
try {
699+
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
700+
} catch (Exception ex) {
701+
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
702+
}
701703

702-
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
703-
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
704-
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
705-
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
706-
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
707-
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
708-
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
709-
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
710-
711-
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
712-
if (PLAYER_INFO_ACTION_CLASS == null) {
713-
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
714-
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
715-
}
704+
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
705+
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
706+
} else {
707+
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
708+
}
716709

717-
try {
718-
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
719-
} catch (Exception ex) {
720-
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
721-
}
710+
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
711+
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
712+
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
713+
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
714+
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
715+
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
716+
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
717+
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
718+
719+
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
720+
if (PLAYER_INFO_ACTION_CLASS == null) {
721+
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
722+
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
723+
}
722724

723-
try {
724-
// TODO enum names are more stable than their packet associations
725-
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
726-
} catch (Exception ex) {
727-
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
728-
}
725+
try {
726+
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
727+
} catch (Exception ex) {
728+
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
729+
}
729730

730-
// In 1.17 the hand and use action class is no longer a field in the packet
731-
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
732-
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
731+
try {
732+
// TODO enum names are more stable than their packet associations
733+
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
734+
} catch (Exception ex) {
735+
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
736+
}
733737

734-
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
735-
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
736-
.parameterCount(0)
737-
.returnTypeMatches(FuzzyMatchers.except(Void.class))
738-
.build());
738+
// In 1.17 the hand and use action class is no longer a field in the packet
739+
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
740+
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
739741

740-
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
741-
} else {
742-
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
743-
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
744-
}
742+
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
743+
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
744+
.parameterCount(0)
745+
.returnTypeMatches(FuzzyMatchers.except(Void.class))
746+
.build());
745747

746-
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
747-
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
748-
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
749-
} else {
750-
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
751-
}
748+
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
749+
} else {
750+
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
751+
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
752+
}
753+
754+
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
755+
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
756+
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
757+
} else {
758+
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
759+
}
760+
761+
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
762+
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
763+
DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot");
764+
765+
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
766+
"world.scores.criteria.ObjectiveCriteria$RenderType",
767+
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
768+
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
769+
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
770+
771+
CLIENT_INTENT_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
772+
773+
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
774+
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
775+
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
776+
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
777+
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
778+
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
779+
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
780+
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
781+
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
782+
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
783+
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
784+
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
785+
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
786+
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
787+
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
788+
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
789+
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
790+
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
791+
associate(HAND_CLASS, Hand.class, getHandConverter());
792+
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
793+
associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter());
794+
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
795+
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
796+
associate(CLIENT_INTENT_CLASS, ClientIntent.class, getClientIntentConverter());
797+
798+
if (ENTITY_POSE_CLASS != null) {
799+
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
800+
}
752801

753-
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
754-
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
755-
DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot");
756-
757-
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
758-
"world.scores.criteria.ObjectiveCriteria$RenderType",
759-
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
760-
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
761-
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
762-
763-
CLIENT_INTENT_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
764-
765-
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
766-
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
767-
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
768-
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
769-
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
770-
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
771-
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
772-
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
773-
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
774-
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
775-
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
776-
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
777-
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
778-
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
779-
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
780-
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
781-
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
782-
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
783-
associate(HAND_CLASS, Hand.class, getHandConverter());
784-
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
785-
associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter());
786-
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
787-
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
788-
associate(CLIENT_INTENT_CLASS, ClientIntent.class, getClientIntentConverter());
789-
790-
if (ENTITY_POSE_CLASS != null) {
791-
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
802+
INITIALIZED = true;
792803
}
793804
}
794805

0 commit comments

Comments
 (0)