UEnhancedInputUserSettings::GetSaveFilename内にてクラッシュが発生します

お疲れ様です。

UE5.6にアップデート後、キーボードショートカットからToggleDebugCameraを実行した際、UEnhancedInputUserSettings::GetSaveFilename内にてクラッシュします。(10回中10回)

▼コールスタック

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000000 at UEnhancedInputUserSettings::LoadOrCreateSettings

0x00007FF7C25FBF33 UEnhancedInputUserSettings::LoadOrCreateSettings() [XXX\Engine\Plugins\EnhancedInput\Source\EnhancedInput\Private\UserSettings\EnhancedInputUserSettings.cpp:638]

0x00007FF7C3A582F6 XXX::PlayerControllerChanged() XXX]

▼概要

ULocalPlayerSubsystem::PlayerControllerChanged 内にて、EnhancedInputの入力を切り替えている処理があるのですが、入力切替時にUEnhancedInputUserSettings::LoadOrCreateSettingsを実行しており、ここでクラッシュが発生しているようです。

パッド操作←→キーボード操作では問題無く、ToggleDebugCameraでは100%クラッシュしておりました。

--

こちら利用手順の問題等、何か分かりませんでしょうか?

よろしくお願いいたします。

再現手順

お世話になっております。

いただいたクラッシュ情報を元にデータベースを調査したのですが、現在のところ、社内や他のライセンシー様、コミュニティから、類似のクラッシュ報告はあがってきていないようです。

そのため、クラッシュの再現を行いたく、利用手順について詳しくヒアリングさせていただきたく存じます。

・キーボードショートカットからToggleDebugCameraを実行するというのは、具体的にはどのような設定、実装を指しておりますでしょうか?

・ULocalPlayerSubsystem::PlayerControllerChanged内の切り替えコードをご提示いただくことは可能でしょうか?

・「パッド操作←→キーボード操作では問題無く、ToggleDebugCameraでは100%クラッシュ」という状況について詳しくご説明いただくことは可能でしょうか?

(キーボードショートカットに「パッド操作←→キーボード操作」と「ToggleDebugCamera」の2種類の操作があり、どちらもPlayerControllerChangedを通過するが、

前者ではクラッシュせず、後者ではクラッシュするということでしょうか?)

・また、Debugビルドで再現をとり、クラッシュ時のコールスタックを(最後の一行ではなく、すべて)頂戴することはできますでしょうか?

もし可能であれば、最小限構成の再現プロジェクトをご共有いただけますと大変助かります。

以上、よろしくお願いいたします。

お疲れ様です。

1.キーボードショートカットからToggleDebugCameraを実行するというのは、具体的にはどのような設定、実装を指しておりますでしょうか?

→PIE実行後、セミコロンキーからのToggleDebugCameraになります。

2.パッド操作←→キーボード操作では問題無く

→操作が切り替わった際に、クラッシュ時と同じ箇所が実行されるのですが、その際は問題ありません。

3.コールスタックは下記になります(プロジェクトコード部分は隠してあります)

UEnhancedInputUserSettings::GetSaveFilename(const ULocalPlayer *) EnhancedInputUserSettings.cpp:795

UEnhancedInputUserSettings::LoadOrCreateSettings(ULocalPlayer *) EnhancedInputUserSettings.cpp:638

>> project

XXXX::PlayerControllerChanged(APlayerController *) XXXX.cpp:384

<< project

[インライン化] UE::Core::Private::Function::TFunctionRefBase::operator()(ULocalPlayerSubsystem *) Function.h:471

`FObjectSubsystemCollection<ULocalPlayerSubsystem>::ForEachSubsystem’::`2’::<lambda_1>::operator()(USubsystem *) SubsystemCollection.h:231

[インライン化] UE::Core::Private::Function::TFunctionRefBase::operator()(USubsystem *) Function.h:471

FSubsystemCollectionBase::ForEachSubsystemOfClass(UClass *, TFunctionRef<…>) SubsystemCollection.cpp:139

[インライン化] FObjectSubsystemCollection::ForEachSubsystem(TFunctionRef<…>, const TSubclassOf<…> &) SubsystemCollection.h:230

ULocalPlayer::ReceivedPlayerController(APlayerController *) LocalPlayer.cpp:737

UCheatManager::EnableDebugCamera() CheatManager.cpp:678

UFunction::Invoke(UObject *, FFrame &, void *const) Class.cpp:7460

[ブループリント] CheatManager: ToggleDebugCamera Function /Script/Engine.CheatManager:ToggleDebugCamera

UObject::ProcessEvent(UFunction *, void *) ScriptCore.cpp:2209

UObject::CallFunctionByNameWithArguments(const wchar_t *, FOutputDevice &, UObject *, bool) ScriptCore.cpp:1494

[インライン化] UObject::ProcessConsoleExec(const wchar_t *, FOutputDevice &, UObject *) Object.h:1520

UCheatManager::ProcessConsoleExec(const wchar_t *, FOutputDevice &, UObject *) CheatManager.cpp:145

UPlayer::Exec(UWorld *, const wchar_t *, FOutputDevice &) Player.cpp:146

ULocalPlayer::Exec(UWorld *, const wchar_t *, FOutputDevice &) LocalPlayer.cpp:1536

UPlayerInput::ExecInputCommands(UWorld *, const wchar_t *, FOutputDevice &) PlayerInput.cpp:2351

UPlayerInput::InputKey(const FInputKeyEventArgs &) PlayerInput.cpp:456

UEnhancedPlayerInput::InputKey(const FInputKeyEventArgs &) EnhancedPlayerInput.cpp:244

APlayerController::InputKey(const FInputKeyEventArgs &) PlayerController.cpp:2397

UGameViewportClient::InputKey(const FInputKeyEventArgs &) GameViewportClient.cpp:770

UCommonGameViewportClient::InputKey(const FInputKeyEventArgs &) CommonGameViewportClient.cpp:60

FSceneViewport::OnKeyDown(const FGeometry &, const FKeyEvent &) SceneViewport.cpp:1088

SViewport::OnKeyDown(const FGeometry &, const FKeyEvent &) SViewport.cpp:288

[インライン化] FSlateApplication::ProcessKeyDownEvent::__l22::<lambda_2>::operator()(const FArrangedWidget &, const FKeyEvent &) SlateApplication.cpp:4850

FEventRouter::Route<FReply,FEventRouter::FBubblePolicy,FKeyEvent,`FSlateApplication::ProcessKeyDownEvent’::`22’::<lambda_2> >(FSlateApplication *,FBubblePolicy,FKeyEvent,const <lambda_2> &,ESlateDebuggingInputEvent) SlateApplication.cpp:462

[インライン化] FEventRouter::RouteAlongFocusPath(FSlateApplication *, FBubblePolicy, FKeyEvent, const <lambda_2> &, ESlateDebuggingInputEvent) SlateApplication.cpp:431

FSlateApplication::ProcessKeyDownEvent(const FKeyEvent &) SlateApplication.cpp:4846

FSlateApplication::OnKeyDown(const int, const unsigned int, const bool) SlateApplication.cpp:4757

FWindowsApplication::ProcessDeferredMessage(const FDeferredWindowsMessage &) WindowsApplication.cpp:2900

FWindowsApplication::DeferMessage(TSharedPtr<…> &, HWND__ *, unsigned int, unsigned long long, long long, int, int, unsigned int) WindowsApplication.cpp:3587

FWindowsApplication::ProcessMessage(HWND__ *, unsigned int, unsigned long long, long long) WindowsApplication.cpp:2742

[インライン化] WindowsApplication_WndProc(HWND__ *, unsigned int, unsigned long long, long long) WindowsApplication.cpp:1720

FWindowsApplication::AppWndProc(HWND__ *, unsigned int, unsigned long long, long long) WindowsApplication.cpp:1725

[インライン化] WinPumpMessages() WindowsPlatformApplicationMisc.cpp:116

FWindowsPlatformApplicationMisc::PumpMessages(bool) WindowsPlatformApplicationMisc.cpp:145

FEngineLoop::Tick() LaunchEngineLoop.cpp:5548

[インライン化] EngineTick() Launch.cpp:60

GuardedMain(const wchar_t *) Launch.cpp:189

LaunchWindowsStartup(HINSTANCE__ *, HINSTANCE__ *, char *, int, const wchar_t *) LaunchWindows.cpp:271

WinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int) LaunchWindows.cpp:339

4.PlayerControllerChanged内のコードになります

XXX.cpp(367)

void XXXX::PlayerControllerChanged(APlayerController* NewPlayerController)
{
	Super::PlayerControllerChanged(NewPlayerController);
 
	ULocalPlayer* localPlayer = GetLocalPlayerChecked();
	if (!localPlayer || !InputMappingSwitcher) { return; }
 
	if (UEnhancedInputLocalPlayerSubsystem* InputSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(localPlayer))
	{
		InputSubsystem->ClearAllMappings();
		if (UEnhancedInputUserSettings* settings = GetValid(InputSubsystem->GetUserSettings()))
		{
			// Engine内の、FString UEnhancedInputUserSettings::GetSaveFilename(const ULocalPlayer* LP) が実行されるのだが、前述のメソッド内でアサーションが発生する場合があるので、実行をブロックする。
			// 
			// if (IsValid(localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
			// 	&& IsValid(localPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()->GetPlayerInput()))
			// {
			settings->LoadOrCreateSettings(localPlayer);
			// }
		}
	}

お世話になっております。

詳細な情報をありがとうございました。おかげさまで同様のプロジェクトを構築することができました。

残念ながら、手元でクラッシュが再現するところまで至れていないのですが、本件はCL39088391の変更の影響と思われます。

この変更はEnhancedInputUserSettingsオブジェクトの保存ファイル名を単一のデフォルト名に固定するのではなく、動的な名前を作成できるようにするために追加されました。そのため、LoadOrCreateSettings()から、新たにファイル名を動的に求めるGetSaveFilename()関数が呼び出されるようになり、その中でPlayerControllerのUPlayerInputオブジェクトを使用するようになりました。しかし、UCheatManager::EnableDebugCamera()からの流れでPlayerControllerChanged()がコールバックされているタイミングでは、まだこのコントローラのInitInputSystem()が呼び出されておらず、PlayerInputがヌルとなり、アサートによるクラッシュを誘発する場合があるものとみております。

本件は、エンジン側での修正が必要な不具合と考えられます。暫定パッチとして、下記コードのように旧来の決め打ちのファイル名を戻し、

namespace UE::EnhancedInput
{
+	/** The name of the slot that these settings will save to */
+	static const FString SETTINGS_SLOT_NAME = TEXT("EnhancedInputUserSettings");

UEnhancedInputUserSettings::GetSaveFilename()内で、PlayerInputをcheck()するのではなく、ヌル時にSETTINGS_SLOT_NAME をreturnするようにすれば、クラッシュを回避できるかと思います。

FString UEnhancedInputUserSettings::GetSaveFilename(const ULocalPlayer* LP)
{
	check(LP);
 
	UEnhancedPlayerInput* PlayerInput = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()->GetPlayerInput();
-	check(PlayerInput);
+	if (PlayerInput == nullptr)
+	{
+		return UE::EnhancedInput::SETTINGS_SLOT_NAME;
+	}
 
	return PlayerInput->GetUserSettingsSaveFileName();
}

お時間のある際にでも、一度お試しいただけますと幸いです。

以上、よろしくお願いいたします。

ご返答ありがとうございます。

こちら確認いたします。