[UE4] エラーデバッグとプログラムエラー

#1. はじめに
　UE4にはプログラム上の様々なエラーを意図的に引き起こすためのDebugコマンドが用意されています。
このコマンドを利用することで意図的なエラーチェックをすることも可能ですが、プログラミングのミスやEngine内部での不具合がどういったことが要因で発生しているか、というプログラムエラーのケースを学ぶこともできます。
　本記事では4.21.1にて確認しています。

#2. エラーと用語
　この項以降で記載している内容や用語について説明しておきます。

**check/fatal/ensure など**
UE4が提供するプログラムの妥当性をチェックするためのマクロを利用したエラーを示します。殆どの内容は、[アサーション](http://api.unrealengine.com/JPN/Programming/Assertions/)にて詳細に説明があります。checkなど一部はShippingにおいても動作を継続しますが、fatalなど一部ではクラッシュが発生してアプリケーションは終了します。

**GPF**
一般保護違反。プログラム上でCPUの仕様に反する例外にないエラー(つまり例外)が発生することを意味します。この場合クラッシュが発生してアプリケーションは終了します。

**ヒッチ**
UE4ではアプリケーションが瞬間的または、継続的に停止する際にこの用語が使用されることが多いです。
例えば膨大なレベルをロードする際などにはスレッドが断定的に停止して画面が停止しているように見る状態となります。

**ハングアップ**
ヒッチと似ていますが、応答が帰らずに画面がフリーズしたままのようなケースで使用されることが多いです。
この場合はアプリケーションを強制的に終了することはありません。

**無限再起/無限ループ**
プログラム上で特定の処理から抜けられずに繰り返し実行するようなケースです。
この場合はアプリケーションを強制的に終了することはありません。

**OOM**
Out Of Memoryの略で使用できるメモリが不足した状態です。
この場合クラッシュが発生してアプリケーションは終了します。

#3. Console Command
　**Shipping以外のビルド構成(Debug/Development/Test)で利用可能**です。エラー種別によっては類似のコードもあるため、いくつかのケースにおいてのみ、そのコードを記載しています。詳しい情報を見たい方は、UnrealEngine.cpp の UEngine::PerformErrorをご参考下さい。コードを見て「なぜエラーとなるのか」を考えてみることもエラー回避のために役に立ちます。

#### Debug RenderCrash
RenderThreadでFatalエラーを発生させるためのRenderCommandを発行します
クラッシュが発生してアプリケーションを終了します

```cpp
ENQUEUE_UNIQUE_RENDER_COMMAND(CauseRenderThreadCrash, { UE_LOG(LogEngine, Warning, TEXT("Printed warning to log.")); SetCrashType(ECrashType::Debug); UE_LOG(LogEngine, Fatal, TEXT("Crashing the renderthread at your request")); });
```
#### Debug RenderCheck
RenderThreadでcheckエラーを発生させるためのRenderCommandを発行します
クラッシュが発生してアプリケーションを終了します
#### Debug RenderGPF
RenderThreadでGPFエラーを発生させるためのRenderCommandを発行します
クラッシュが発生してアプリケーションを終了します

```cpp
ENQUEUE_UNIQUE_RENDER_COMMAND(CauseRenderThreadCrash, { UE_LOG(LogEngine, Warning, TEXT("Printed warning to log.")); SetCrashType(ECrashType::Debug); *(int32 *)3 = 123; });
```
#### Debug RenderFatal
RenderThreadでLowLevelFatalErrorを発生させるためのRenderCommandを発行します
クラッシュが発生してアプリケーションを終了します
#### Debug RenderEnsure
RenderThreadでensureエラーを発生させるためのRenderCommandを発行します
クラッシュが発生してアプリケーションを終了します
#### Debug ThreadCrash
Debug用のThreadでFatalエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug TheradCheck
Debug用のThreadでcheckエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug TheradGPF
Debug用のThreadでGPFエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug TheradFatal
Debug用のThreadでFatalエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug TheradEnsure
Debug用のThreadでensureエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug TwoThreadsCrash
Debug用の2つのThreadでFatalエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug TwoThreadGPF
Debug用の2つのThreadでGPFエラーを発生させます
クラッシュが発生してアプリケーションを終了します
#### Debug Crash
Fatalエラーによりクラッシュが発生してアプリケーションを終了します

```cpp
		UE_LOG(LogEngine, Fatal, TEXT("%s"), TEXT("Crashing the gamethread at your request"));
```
#### Debug Check
checkエラーによりクラッシュが発生してアプリケーションを終了します

```cpp
		check(!"Crashing the game thread via check(0) at your request");
```
#### Debug GPF
GPFエラーによりクラッシュが発生してアプリケーションを終了します

```cpp
		*(int32 *)3 = 123;
```
#### Debug Ensure
ensureエラーによりクラッシュが発生してアプリケーションを終了します

```cpp
		if (!ensure(0))
		{
			return true;
		}
```
#### Debug EnsureAlways
ensureAlwaysエラーによりクラッシュが発生してアプリケーションを終了します

```cpp
		if (!ensureAlways(0))
		{
			return true;
		}
```
#### Debug Fatal
LowLevelFatalErrorエラーによりクラッシュが発生してアプリケーションを終了します

```cpp
		LowLevelFatalError(TEXT("FError::LowLevelFatal test"));
```
#### Debug BufferOverRun
BufferOverRunによりクラッシュが発生してアプリケーションを終了します

```cpp
		ANSICHAR SrcBuffer[] = "12345678901234567890123456789012345678901234567890";
		BufferOverflowFunction(ARRAY_COUNT(SrcBuffer), SrcBuffer);
```
#### Debug CRTInvalid
~~CRT(C-Runtime Library)エラーを発生します~~
現在機能しません
#### Debug Hitch [TIME]
意図的なHitch(Sleep)を指定時間(ms)発生します
時間指定が無い場合は1000ms(1s)が選択されます

```cpp
		float Seconds = FCString::Atof(Cmd) / 1000.0f;
		if (Seconds == 0.0f)
		{
			Seconds = 1.0f;
		}
		FPlatformProcess::Sleep(Seconds);
```
#### Debug Spin [TIME]
ThreadにSpinlock(whileループロック)を指定時間(ms)発生します
時間指定が無い場合は1000ms(1s)が選択されます
#### Debug RenderSpin [TIME]
RenderThreadでSpinlock(whileループロック)を指定時間(ms)発生させるためのRenderCommandを発行します
時間指定が無い場合は1000ms(1s)が選択されます
#### Debug LongLog
2048Byteを超えるログを出力します
#### Debug Recurse
再帰呼び出しを実行しアプリケーションをクラッシュします
#### Debug ThreadRecurse
Threadで再帰呼び出しを実行しアプリケーションをクラッシュします
#### Debug EatMem
強制的にメモリを確保し続けることで、利用可能なメモリを全て食い潰してクラッシュを発生します

```cpp
		while (1)
		{
			void* Eat = FMemory::Malloc(65536);
			FMemory::Memset(Eat, 0, 65536);
		}
```
#### Debug OOM
~~OOMを発生させるまで強制的にメモリを確保し続けます~~
現在機能しません
#### Debug StackOverflow
再帰的関数の中でMemsetを実行しStackOverflowによるクラッシュを発生します
#### Debug ThreadStackOverflow
Threadの中で再帰的関数の中でMemsetを実行しStackOverflowによるクラッシュを発生します
#### Debug SoftLock
Threadのwhile無限ループ内でsleep処理を実行しアプリケーションをハングアップします
#### Debug InfiniteLoop
InfiniteLoop(for無限ループ処理)を実行しアプリケーションをハングアップします

```cpp
		for(;;)
		{
		}
```
#### Debug Sleep
1hour(3600sec)のsleep処理を実行しアプリケーションをハングアップします

```cpp
		FPlatformProcess::Sleep(3600);
```
#### Debug AudioGPF
AudioThreadにてGPFエラーを実行しクラッシュを発生します

#4. まとめ
　エラーテストは大抵の場合、開発やデバッグの終盤で実施されるということがしばしばありますが、このように多くのコンソールコマンドが最初から用意されているため先んじて実施することも容易です。特にエラーテストは自身でエラーケースとなるコードを書いて、リリース時には削除して、といった結局書いたコードを無駄にしてしまうのであれば、こうした既存の機能を利用しない手はありません。できるだけ早期から検証することで出来るだけ手戻りが不要な状態を維持することは大切です。