Rubyの例外のうち StandardError に属さないものを発生させる実験

Rubyの例外クラスは全て `Exception` のサブクラスだが、そのうち**プログラムの継続実行が困難なエラー**は `StandardError` のサブクラスではなく、例外の種類を省略した `rescue` では捕捉しないようになっている。

[制御構造 例外処理: begin - Rubyリファレンスマニュアル](https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html#begin)

> 例外の一致判定は，発生した例外が rescue 節で指定したクラスのインスタンスであるかどうかで行われます。
> 
> error_type が省略された時は `StandardError` のサブクラスである全ての例外を捕捉します。Rubyの組み込み例外は(`SystemExit` や `Interrupt` のような脱出を目的としたものを除いて) `StandardError` のサブクラスです。

```ruby:test_rescue.rb
begin
	raise StandardError, 'test'
rescue => e
	p e
end
#=> #<StandardError: test>

begin
	raise Exception, 'test'
rescue => e
	p e
end
# Traceback (most recent call last):
# test_rescue.rb:9:in `<main>': test (Exception)
```

もちろんこの例のように `raise` 時に例外の種類を明示すれば簡単に発生させられるが、それ以外の場合では（一部を除いて）滅多に見ることがない。実際の例を見てみたくなったので試してみる。

## 例外の一覧
実際にコードを実行して一覧を手に入れる。

```ruby:list_exceptions.rb
pp ObjectSpace.each_object(Class)
	.select { |cls| cls <= Exception }
	.reject { |cls| cls <= StandardError }
	.group_by(&:superclass)
```

結果を継承関係で木構造にまとめると次の通りになる。

- `Exception`
	- `NoMemoryError`
	- `ScriptError`
		- `LoadError`
			- `Gem::LoadError`
				- `Gem::ConflictError`
				- `Gem::MissingSpecError`
					- `Gem::MissingSpecVersionError`
		- `NotImplementedError`
		- `SyntaxError`
	- `SecurityError`
	- `SignalException`
		- `Interrupt`
	- `SystemExit`
		- `Gem::SystemExitException`
	- `SystemStackError`
	- `fatal`
	- `MonitorMixin::ConditionVariable::Timeout`

<!--
	- (`IRB::Abort`)
	- (`Pry::InputLock::Interrupt`)
-->

[`Gem`](https://docs.ruby-lang.org/ja/latest/library/rubygems.html) や [`MonitorMixin`](https://docs.ruby-lang.org/ja/latest/library/monitor.html) にも例外が存在するようだが、その他はリファレンスマニュアルの[組み込みライブラリ](https://docs.ruby-lang.org/ja/latest/library/_builtin.html)の内容と一致する。

## 例外発生実験
`raise` で明示せず出せる例外を試していく。コードは全て `begin` ～ `rescue` ～ `end` にしてあり、 `rescue Exception => e` と直せば例外を捕捉して表示する。

### `Exception`

> 全ての例外の祖先のクラスです。

恐らく継承のために用意しているもので、明示せずに出せるものではないと思う。

### `NoMemoryError`

> メモリの確保に失敗すると発生します。

という単純明快な例外。プログラムを継続実行しづらいことも納得しやすい。

しかしメモリを大量に消費しないといけないので、他のプロセスにも影響が出そうで気軽に試すのは怖い。以下はWindows Subsystem for Linux (WSL)上で影響なく動いたが、実行は自己責任で。

```ruby:no_memory_error.rb
begin
	String.new(capacity: 2 ** 40) # 1 TiB
rescue => e
	p e
end
# Traceback (most recent call last):
# no_memory_error.rb: failed to allocate memory (NoMemoryError)
```

### `ScriptError`

> スクリプトのエラーを表す例外クラスです。
> 
> 以下の例外クラスのスーパークラスです。
> 
> * `LoadError`
> * `NotImplementedError`
> * `SyntaxError`
> 
> これらの例外が発生したときは Ruby スクリプト自体にバグがある可能性が高いと考えられます。

これも恐らく継承のために用意しているもので、明示せずに出せるものではないと思う。

### `LoadError`

> `Kernel.#require` や `Kernel.#load` が失敗したときに発生します。

ライブラリ名を間違えたりすれば出るので、よくお世話になる。

```ruby:load_error.rb
begin
	require 'this/file/does/not/exist'
rescue => e
	p e
end
# Traceback (most recent call last):
# 	2: from load_error.rb:2:in `<main>'
# 	1: from $(gem environment gemdir)/rubygems/core_ext/kernel_require.rb:54:in `require'
# $(gem environment gemdir)/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- this/file/does/not/exist (LoadError)
```

###  `NotImplementedError`

> 実装されていない機能が呼び出されたときに発生します。

恐らくユーザー（やライブラリ）が使うために用意された例外。明示する以外に出す方法は無いと思う。Rubyのソースコードで `NotImplementedError` や `rb_eNotImpError` を検索すると、いくつかの場所で使われていることがわかる。

```ruby:not_implemented_error.rb
begin
	require 'digest'
	puts Digest::Base.hexdigest('test')
rescue => e
	p e
end
# Traceback (most recent call last):
# 	2: from not_implemented_error.rb:3:in `<main>'
# 	1: from not_implemented_error.rb:3:in `hexdigest'
# not_implemented_error.rb:3:in `digest': Digest::Base is an abstract class (NotImplementedError)
```

これまで未実装の部分やオーバーライド前提のメソッドに `raise 'Not implemented.'` なんて仕込んできた（→到達すると `RuntimeError` になる）が、こっちの例外を使うのが正しかった？

### `SyntaxError`

> ソースコードに文法エラーがあったときに発生します。

これもよくお世話になる。とはいえ試すとなると、コード読み込み時に文法チェックに引っかかったら実行できないので、 `eval` 内でわざと間違えることにする。

```ruby:syntax_error.rb
begin
	eval ')'
rescue => e
	p e
end
# Traceback (most recent call last):
# 	1: from syntax_error.rb:2:in `<main>'
# syntax_error.rb:2:in `eval': (eval):1: syntax error, unexpected ')' (SyntaxError)
```

```shell-session:terminal
$ ruby -c syntax_error.rb
Syntax OK

$ ruby syntax_error.rb
Traceback (most recent call last):
	1: from syntax_error.rb:2:in `<main>'
syntax_error.rb:2:in `eval': (eval):1: syntax error, unexpected ')' (SyntaxError)
```

### `SecurityError`

> セキュリティ上の問題が起きたときに発生します。
> 
> [セキュリティモデル](https://docs.ruby-lang.org/ja/latest/doc/spec=2fsafelevel.html)も参照してください。

使ったことのない仕組みなのでピンとこないが、とりあえず `eval` で試してみる。さっきと同じ文法エラーのコードを与えても、「汚染させた」場合はそもそも実行されなくなる。

```ruby:security_error.rb
begin
	$SAFE = 1
	eval ')'.taint
rescue => e
	p e
end
# Traceback (most recent call last):
# 	1: from security_error.rb:3:in `<main>'
# security_error.rb:3:in `eval': Insecure operation - eval (SecurityError)
```

### `SignalException`

> 捕捉していないシグナルを受け取ったときに発生します。

[`Signal.#trap`](https://docs.ruby-lang.org/ja/latest/method/Signal/m/trap.html) で割り込みシグナルをハンドリングしないと発生する。

[`Process.#kill`](https://docs.ruby-lang.org/ja/latest/method/Process/m/kill.html) で自身のプロセスに SIGTERM を送ってみる。

```ruby:signal_exeption.rb
begin
	Process.kill(:TERM, Process.pid)
rescue => e
	p e
end
# Terminated
```

[リファレンスマニュアル](https://docs.ruby-lang.org/ja/latest/class/SignalException.html)に書いてあるが、全てのシグナルに対応するわけではない。例えば SIGSEGV を送ると例外すら出ずRubyがコアダンプして異常終了する。

### `Interrupt`

> SIGINT シグナルを捕捉していないときに SIGINT シグナルを受け取ると発生します。 SIGINT 以外のシグナルを受信したときに発生する例外については `SignalException` を参照してください。

こちらはコード実行を Ctrl+C で中断したりすれば出るのでよく見かける。

```ruby:interrupt.rb
begin
	Process.kill(:INT, Process.pid)
rescue => e
	p e
end
# Traceback (most recent call last):
# 	1: from interrupt.rb:2:in `<main>'
# interrupt.rb:2:in ``': Interrupt
```

### `SystemExit`

> Ruby インタプリタを終了させるときに発生します。

例外自身のページよりも[終了処理](https://docs.ruby-lang.org/ja/latest/doc/spec=2fterminate.html)のページのほうが説明が詳しい。

> 関数 `Kernel.#exit` や `Kernel.#abort` 、メインスレッドに対する `Thread.kill` などは `SystemExit` 例外を発生させます

ということは、スクリプトの途中で `exit` などしても捕捉すれば終了を取り消せる。

逆に、例外として異常終了させる（Tracebackを表示させる）ことはできなさそう。

```ruby:system_exit.rb
begin
	abort 'test'
rescue => e
	p e
end
# test
```

```shell-session:terminal
$ ruby system_exit.rb # Exceptionを捕捉しない場合
test
$ echo $?             # --> abortで終わったので終了ステータスは0でない
1

$ ruby system_exit.rb # Exceptionを捕捉した場合
test
#<SystemExit: test>
$ echo $?             # --> スクリプト終端に達したので終了ステータスは0
0
```

### `SystemStackError`

> システムスタックがあふれたときに発生します。
> 
> 典型的には、メソッド呼び出しを無限再帰させてしまった場合に発生します。

再帰の練習などしていれば見るはず。

```ruby:system_stack_error.rb
def factorial(n)
	return 1 if n == 0

	n * factorial(n - 1)
end

begin
	p factorial(-1)
rescue => e
	p e
end
# Traceback (most recent call last):
# 	10080: from system_stack_error.rb:8:in `<main>'
# 	10079: from system_stack_error.rb:4:in `factorial'
# 	10078: from system_stack_error.rb:4:in `factorial'
# 	10077: from system_stack_error.rb:4:in `factorial'
# 	10076: from system_stack_error.rb:4:in `factorial'
# 	10075: from system_stack_error.rb:4:in `factorial'
# 	10074: from system_stack_error.rb:4:in `factorial'
# 	10073: from system_stack_error.rb:4:in `factorial'
# 	 ... 10068 levels...
# 	    4: from system_stack_error.rb:4:in `factorial'
# 	    3: from system_stack_error.rb:4:in `factorial'
# 	    2: from system_stack_error.rb:4:in `factorial'
# 	    1: from system_stack_error.rb:4:in `factorial'
# system_stack_error.rb:4:in `factorial': stack level too deep (SystemStackError)
```

### `fatal`

> インタプリタ内部で致命的なエラーが起こったときに発生します。
> 
> 致命的なエラーとは、例えば以下のような状態です。
> 
> * スレッドのデッドロックが発生した
> * -x オプションや -C オプションで指定されたディレクトリに移動できなかった
> * -i オプション付きで起動されたが、 パーミッションなどの関係でファイルを変更できなかった
> 
> 通常の手段では、 Ruby プログラムからは fatal クラスにはアクセスできません。

小文字で始まっているけれどもクラス。モジュール全体を探しても小文字始まりはこれしか無い。普通にコードに `fatal` と書いても、変数かメソッドと判断されて例外を参照できない。

```ruby
ary = ObjectSpace.each_object(Module).select { |mdl| /^[a-z]/ =~ mdl.to_s }
e = ary.first
raise e, 'test'
```

致命的なエラーとして挙げられているデッドロックを起こしてみる。（参考：[The Backyard - DeadLockInRuby](https://www.artonx.org/collabo/backyard/?DeadLockInRuby)）

```ruby:fatal.rb
begin
	q = Queue.new
	Thread.new { q.deq }.join
rescue => e
	p e
end
# Traceback (most recent call last):
# 	1: from fatal.rb:3:in `<main>'
# fatal.rb:3:in `join': No live threads left. Deadlock? (fatal)
# 2 threads, 2 sleeps current:0x00007fffcf775500 main thread:0x00007fffcf3b7470
# * #<Thread:0x00007fffcf3e72c8 sleep_forever>
#    rb_thread_t:0x00007fffcf3b7470 native:0x00007f9e3def0700 int:0
#    fatal.rb:3:in `join'
#    fatal.rb:3:in `<main>'
# * #<Thread:0x00007fffcf7ac188@fatal.rb:3 sleep_forever>
#    rb_thread_t:0x00007fffcf775500 native:0x00007f9e39f90700 int:0
#     depended by: tb_thread_id:0x00007fffcf3b7470
#    fatal.rb:3:in `pop'
#    fatal.rb:3:in `block in <main>'
```