The GameFrameworkComponentManager has a system for registering callbacks to execute in response to event changes. This ends up calling UGameFrameworkComponentManager::CallFeatureStateDelegates which tries to call multiple delegates in a safe way. However, it does not make a copy of the delegate before calling which means that if the RegisteredDelegates array is resized from a callback, it may cause memory problems and crash. The function itself protects against this, but the delegate execution logic does not handle being destroyed in the middle of execution.
The code needs to be changed to either make copies of delegates, or stop it from modifying the raw delegate memory during execution by adding to some sort of list of pending changes that are applied after the delegate execution finishes
This is based on a licensee report, here is a Lyra test case that illustrates the problem:
UFUNCTION() void ChangeCallback1(const FActorInitStateChangedParams& Params) { } UFUNCTION() void ChangeCallback2(const FActorInitStateChangedParams& Params) { } UFUNCTION() void ChangeCallback3(const FActorInitStateChangedParams& Params) { } UFUNCTION() void ChangeCallback4(const FActorInitStateChangedParams& Params) { } UFUNCTION() void ChangeCallback5(const FActorInitStateChangedParams& Params) { } UFUNCTION() void ChangeCallback6(const FActorInitStateChangedParams& Params) { }
FActorInitStateChangedBPDelegate BindDelegate; BindDelegate.FActorInitStateChangedBPDelegate::BindDynamic(this, &ULyraPawnExtensionComponent::ChangeCallback1); RegisterAndCallForInitStateChange(Params.FeatureState, BindDelegate); BindDelegate.FActorInitStateChangedBPDelegate::BindDynamic(this, &ULyraPawnExtensionComponent::ChangeCallback2); RegisterAndCallForInitStateChange(Params.FeatureState, BindDelegate); BindDelegate.FActorInitStateChangedBPDelegate::BindDynamic(this, &ULyraPawnExtensionComponent::ChangeCallback3); RegisterAndCallForInitStateChange(Params.FeatureState, BindDelegate); BindDelegate.FActorInitStateChangedBPDelegate::BindDynamic(this, &ULyraPawnExtensionComponent::ChangeCallback4); RegisterAndCallForInitStateChange(Params.FeatureState, BindDelegate); BindDelegate.FActorInitStateChangedBPDelegate::BindDynamic(this, &ULyraPawnExtensionComponent::ChangeCallback5); RegisterAndCallForInitStateChange(Params.FeatureState, BindDelegate); BindDelegate.FActorInitStateChangedBPDelegate::BindDynamic(this, &ULyraPawnExtensionComponent::ChangeCallback6); RegisterAndCallForInitStateChange(Params.FeatureState, BindDelegate);
This is the stack of where the memory corruption occurs, not the stack of an actual crash which will be random. It will not corrupt until the third delegate is bound which resizes the array:
UnrealEditor-ModularGameplay.dll!UGameFrameworkComponentManager::RegisterAndCallForActorInitState(AActor * Actor, FName FeatureName, FGameplayTag RequiredState, FActorInitStateChangedBPDelegate Delegate, bool bCallImmediately) Line 819 UnrealEditor-ModularGameplay.dll!IGameFrameworkInitStateInterface::RegisterAndCallForInitStateChange(FGameplayTag RequiredState, FActorInitStateChangedBPDelegate Delegate, bool bCallImmediately) Line 239 UnrealEditor-LyraGame.dll!ULyraPawnExtensionComponent::OnActorInitStateChanged(const FActorInitStateChangedParams & Params) Line 296 [Inline Frame] UnrealEditor-ModularGameplay.dll!TDelegate<void __cdecl(FActorInitStateChangedParams const &),FDefaultDelegateUserPolicy>::Execute(const FActorInitStateChangedParams &) Line 549 UnrealEditor-ModularGameplay.dll!UGameFrameworkComponentManager::FActorFeatureRegisteredDelegate::Execute(AActor * OwningActor, FName FeatureName, UObject * Implementer, FGameplayTag FeatureState) Line 552 UnrealEditor-ModularGameplay.dll!UGameFrameworkComponentManager::CallFeatureStateDelegates(AActor * Actor, UGameFrameworkComponentManager::FActorFeatureState StateChange) Line 1044 UnrealEditor-ModularGameplay.dll!UGameFrameworkComponentManager::ProcessFeatureStateChange(AActor * Actor, const UGameFrameworkComponentManager::FActorFeatureState * StateChange) Line 1010 UnrealEditor-ModularGameplay.dll!UGameFrameworkComponentManager::ChangeFeatureInitState(AActor * Actor, FName FeatureName, UObject * Implementer, FGameplayTag FeatureState) Line 725 UnrealEditor-ModularGameplay.dll!IGameFrameworkInitStateInterface::TryToChangeInitState(FGameplayTag DesiredState) Line 98 UnrealEditor-LyraGame.dll!ULyraHeroComponent::BeginPlay() Line 215 UnrealEditor-Engine.dll!AActor::BeginPlay() Line 4231 UnrealEditor-Engine.dll!APawn::BeginPlay() Line 179 UnrealEditor-LyraGame.dll!ALyraCharacter::BeginPlay() Line 92 UnrealEditor-Engine.dll!AActor::DispatchBeginPlay(bool bFromLevelStreaming) Line 4193 UnrealEditor-Engine.dll!AActor::PostActorConstruction() Line 3979 UnrealEditor-Engine.dll!AActor::FinishSpawning(const UE::Math::TTransform<double> & UserTransform, bool bIsDefaultTransform, const FComponentInstanceDataCache * InstanceDataCache, ESpawnActorScaleMethod TransformScaleMethod) Line 3878 UnrealEditor-LyraGame.dll!ALyraGameMode::SpawnDefaultPawnAtTransform_Implementation(AController * NewPlayer, const UE::Math::TTransform<double> & SpawnTransform) Line 368 UnrealEditor-Engine.dll!AGameModeBase::execSpawnDefaultPawnAtTransform(UObject * Context, FFrame & Stack, void * const Z_Param__Result) Line 1511 UnrealEditor-CoreUObject.dll!UFunction::Invoke(UObject * Obj, FFrame & Stack, void * const Z_Param__Result) Line 6921 UnrealEditor-CoreUObject.dll!UObject::ProcessEvent(UFunction * Function, void * Parms) Line 2144 UnrealEditor-Engine.dll!AActor::ProcessEvent(UFunction * Function, void * Parameters) Line 1092 UnrealEditor-Engine.dll!AGameModeBase::SpawnDefaultPawnAtTransform(AController * NewPlayer, const UE::Math::TTransform<double> & SpawnTransform) Line 1464 UnrealEditor-Engine.dll!AGameModeBase::SpawnDefaultPawnFor_Implementation(AController * NewPlayer, AActor * StartSpot) Line 1226 UnrealEditor-Engine.dll!AGameModeBase::execSpawnDefaultPawnFor(UObject * Context, FFrame & Stack, void * const Z_Param__Result) Line 1582 UnrealEditor-CoreUObject.dll!UFunction::Invoke(UObject * Obj, FFrame & Stack, void * const Z_Param__Result) Line 6921 UnrealEditor-CoreUObject.dll!UObject::ProcessEvent(UFunction * Function, void * Parms) Line 2144 UnrealEditor-Engine.dll!AActor::ProcessEvent(UFunction * Function, void * Parameters) Line 1092 UnrealEditor-Engine.dll!AGameModeBase::SpawnDefaultPawnFor(AController * NewPlayer, AActor * StartSpot) Line 1536 UnrealEditor-Engine.dll!AGameModeBase::RestartPlayerAtPlayerStart(AController * NewPlayer, AActor * StartSpot) Line 1299 UnrealEditor-Engine.dll!AGameModeBase::RestartPlayer(AController * NewPlayer) Line 1265
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-208462 in the post.
0 |
Component | UE - Gameplay |
---|---|
Affects Versions | 5.5, 5.2 |
Target Fix | 30.00, 5.5 |
Fix Commit | 33049490 |
---|
Created | Feb 28, 2024 |
---|---|
Resolved | Apr 17, 2024 |
Updated | Jun 12, 2024 |