# 機能設計書 51-catch/throw

## 概要

本ドキュメントは、mrubyにおける非ローカルジャンプ機能（catch/throw）の設計を記述する。catch/throwはRuby標準の制御フロー構文であり、ネストされたループや関数呼び出しを横断して制御を転送するための仕組みを提供する。

### 本機能の処理概要

catch/throwは、例外処理（begin/rescue）とは異なる非ローカルジャンプ機構を提供する。catchブロック内のどの深さからでもthrowを呼び出すことで、対応するcatchブロックの末尾まで制御を即座に転送できる。

**業務上の目的・背景**：深くネストされた処理ループや再帰的な関数呼び出しの中から、特定の条件が満たされた際に一気に制御を脱出したい場合がある。例外処理（raise/rescue）は本来エラー処理のためのものであり、正常な制御フローの脱出にはcatch/throwを使用すべきである。これにより、コードの意図が明確になり、例外処理のオーバーヘッドを回避できる。

**機能の利用シーン**：多重ループからの脱出、再帰処理の中断、複雑な探索アルゴリズムにおける結果確定時の即時リターンなど、正常系の制御フローとして使用される。

**主要な処理内容**：
1. catchメソッドがタグ（識別子）を指定してブロックを実行する
2. throwメソッドが対応するタグを持つcatchブロックを検索する
3. 一致するcatchブロックが見つかった場合、RBreakオブジェクトを生成して制御を転送する
4. 一致するcatchブロックが見つからない場合、UncaughtThrowErrorを発生させる

**関連システム・外部連携**：VM（仮想マシン）のコールスタック管理機構と連携して動作する。RBreakオブジェクトによる制御転送はVMの例外処理メカニズムを利用する。

**権限による制御**：特になし。catch/throwはKernelモジュールのプライベートメソッドとして定義され、全オブジェクトから利用可能である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | CLIおよびランタイム機能であり、画面との直接的関連なし |

## 機能種別

制御フロー処理（非ローカルジャンプ）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| tag（catch） | Object | No | catchブロックの識別子。省略時はObject.newが自動生成される | 任意のオブジェクト |
| block（catch） | Block | Yes | catchブロックとして実行されるブロック | ブロック必須 |
| tag（throw） | Object | Yes | 制御を転送する先のcatchタグ | 任意のオブジェクト |
| obj（throw） | Object | No | catchブロックの戻り値。省略時はnil | 任意のオブジェクト |

### 入力データソース

Rubyプログラムからの直接呼び出し。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| catchの戻り値 | Object | throwが呼ばれた場合はthrowの第2引数（省略時nil）。throwなしの場合はブロックの戻り値 |

### 出力先

呼び出し元のRubyプログラムへの戻り値。

## 処理フロー

### 処理シーケンス

```
1. catch(tag, &block)の呼び出し
   └─ tagが省略された場合、Object.newで新しいタグオブジェクトを生成
2. ブロックにタグを引数として渡して実行
   └─ block.call(tag)を実行
3. ブロック内でthrowが呼ばれた場合
   └─ find_catcher()でコールスタックを逆走査し、一致するcatch_procを検索
4. 一致するcatchブロックが見つかった場合
   └─ RBreakオブジェクトを生成し、ci_break_indexに戻り先を設定
   └─ mrb_exc_raise()で制御を転送
5. 一致するcatchブロックが見つからない場合
   └─ UncaughtThrowErrorを発生させる
6. throwが呼ばれなかった場合
   └─ ブロックの戻り値をcatchの戻り値として返す
```

### フローチャート

```mermaid
flowchart TD
    A[catch tag, &block 呼び出し] --> B{tag指定あり?}
    B -->|Yes| C[tagをそのまま使用]
    B -->|No| D[Object.newでtag生成]
    C --> E[block.call tag 実行]
    D --> E
    E --> F{throw呼び出し?}
    F -->|No| G[ブロック戻り値を返す]
    F -->|Yes| H[find_catcher でコールスタック検索]
    H --> I{一致するcatch発見?}
    I -->|Yes| J[RBreak生成 + ci_break_index設定]
    J --> K[mrb_exc_raiseで制御転送]
    K --> L[throwの第2引数をcatchの戻り値に]
    I -->|No| M[UncaughtThrowError発生]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | タグの同一性判定 | catchとthrowのタグはmrb_obj_eq（オブジェクト同一性）で比較される。等価性（==）ではなく同一性（equal?）で判定 | throwが呼ばれた時 |
| BR-51-02 | ネスト時の動作 | 同じタグで複数のcatchがネストされている場合、最も内側のcatchに制御が渡る | 同一タグのcatchがネストされた時 |
| BR-51-03 | throwの戻り値 | throwの第2引数が省略された場合、catchの戻り値はnilとなる | throwに第2引数がない時 |
| BR-51-04 | rescueとの関係 | throw/catchによる制御転送は通常の例外（rescue）では捕捉されない | throwがrescue内で呼ばれた場合 |

### 計算ロジック

特になし。

## データベース操作仕様

### 操作別データベース影響一覧

該当なし（データベース操作を行わない）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | UncaughtThrowError | 対応するcatchブロックが存在しないタグでthrowが呼ばれた場合 | 適切なcatchブロックを配置する。エラーメッセージにはタグと値が含まれる |

### リトライ仕様

リトライ不要。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- find_catcher関数はコールスタックをO(n)で線形探索する（nはコールスタック深度）
- catchメソッドは事前コンパイルされたバイトコード（catch_iseq）として実装されており、通常のRubyメソッド呼び出しと同等のオーバーヘッド
- throwは通常の例外発生と同様のRBreakメカニズムを使用するため、発生時にはGCアリーナへの割り当てが発生する

## セキュリティ考慮事項

catchとthrowはKernelモジュールのプライベートメソッドとして定義されている。レシーバなしでのみ呼び出し可能であり、外部からの不正な制御フロー操作のリスクは限定的。

## 備考

- catchメソッドは効率化のため、C言語で手書きされたバイトコード（catch_iseq）として実装されている
- catch_proc構造体は静的に定義されており、コールスタック内でcatchブロックの識別子として使用される
- UncaughtThrowErrorはArgumentErrorのサブクラスとしてRuby（mrblib/catch.rb）で定義されている

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、catch/throwで使われるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | catch.c | `mrbgems/mruby-catch/src/catch.c` | catch_syms（3つのシンボル: Object, new, call）の定義（10行目） |
| 1-2 | catch.c | `mrbgems/mruby-catch/src/catch.c` | catch_iseq（バイトコード配列）の構造（21-37行目） |
| 1-3 | catch.c | `mrbgems/mruby-catch/src/catch.c` | catch_irep（命令表現）の静的定義（40-47行目） |
| 1-4 | catch.c | `mrbgems/mruby-catch/src/catch.c` | catch_proc（プロシージャオブジェクト）の静的定義（50-54行目） |

**読解のコツ**: catch_iseqはmrubyバイトコードを直接記述した配列である。OP_ENTER, OP_JMP, OP_MOVE, OP_GETCONST, OP_SEND, OP_RETURNの各オペコードの意味を理解すると、`def catch(r1 = Object.new, &r2); r2.call(r1); end`と等価であることが分かる。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | catch.c | `mrbgems/mruby-catch/src/catch.c` | mrb_mruby_catch_gem_init()でのcatch/throwメソッド登録（129-140行目） |

**主要処理フロー**:
1. **134行目**: catch_symsのプリシンボル初期化
2. **135-137行目**: catch_procをKernelモジュールのcatchメソッドとして登録（プライベート）
3. **139行目**: throw_mをKernelモジュールのthrowメソッドとして登録（プライベート）

#### Step 3: throwの処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | catch.c | `mrbgems/mruby-catch/src/catch.c` | throw_m()関数 - throwメソッドの実装（97-116行目） |
| 3-2 | catch.c | `mrbgems/mruby-catch/src/catch.c` | find_catcher()関数 - コールスタック探索（57-71行目） |

**主要処理フロー**:
- **101行目**: 引数パース。tag（必須）とobj（省略可、デフォルトnil）を取得
- **105行目**: find_catcher()でコールスタックを検索
- **106-109行目**: 見つからない場合、UncaughtThrowErrorを発生
- **110-113行目**: 見つかった場合、RBreakオブジェクトを生成してmrb_exc_raiseで制御転送
- **60行目**: find_catcherはci（コールインフォ）を逆方向に走査
- **65行目**: ci->procがcatch_procと一致し、かつスタック上の第1引数がtagと同一（mrb_obj_eq）であるかを判定

#### Step 4: UncaughtThrowErrorを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | catch.rb | `mrbgems/mruby-catch/mrblib/catch.rb` | UncaughtThrowErrorクラス定義（1-29行目） |

**主要処理フロー**:
- **5行目**: ArgumentErrorを継承
- **7-8行目**: tag, valueの属性リーダー定義
- **24-28行目**: initializeでtag/valueを保存し、メッセージを生成

### プログラム呼び出し階層図

```
Kernel#catch (catch_proc / catch_iseq バイトコード)
    |
    +-- Object.new (タグ省略時にデフォルトタグ生成)
    +-- block.call(tag)
         |
         +-- Kernel#throw (throw_m)
              |
              +-- find_catcher()
              |    +-- コールスタック逆走査
              |    +-- catch_procとの一致判定
              |    +-- mrb_obj_eq()でタグ同一性判定
              |
              +-- [一致あり] RBreak生成 + mrb_exc_raise()
              +-- [一致なし] UncaughtThrowError生成 + mrb_exc_raise()
```

### データフロー図

```
[入力]                     [処理]                          [出力]

tag (Object)     ──────> catch_proc (バイトコード実行)  ──> catchの戻り値
block (Proc)     ──────> block.call(tag) 実行
                          |
                          v
tag (Object)     ──────> throw_m()
obj (Object/nil) ──────>   find_catcher() ─────────────> RBreak.value
                          |                                (制御転送後の戻り値)
                          v [不一致時]
                         UncaughtThrowError ───────────> 例外発生
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| catch.c | `mrbgems/mruby-catch/src/catch.c` | ソース | catch/throwのC実装（バイトコード定義、throw処理、gem初期化） |
| catch.rb | `mrbgems/mruby-catch/mrblib/catch.rb` | ソース | UncaughtThrowErrorクラスのRuby実装 |
| catch.rb | `mrbgems/mruby-catch/test/catch.rb` | テスト | catch/throwの動作テスト |
| mrbgem.rake | `mrbgems/mruby-catch/mrbgem.rake` | 設定 | gemメタデータ定義 |
