# 機能設計書 80-Doctorモード

## 概要

本ドキュメントは、Lua Language Serverにおけるメモリ診断機能（Doctor Mode）の設計を記述する。この機能は、Luaバーチャルマシン内のオブジェクト参照を追跡し、メモリリークの検出やメモリ使用状況の分析を支援する。

### 本機能の処理概要

**業務上の目的・背景**：Lua Language Serverは長時間稼働するプロセスであり、メモリリークが発生すると時間とともにメモリ使用量が増加する。本機能はLuaバーチャルマシン内のオブジェクト参照関係を可視化し、メモリリークの原因特定を支援する。開発者やメンテナがメモリ問題をデバッグする際に使用される。

**機能の利用シーン**：
- メモリリークの原因調査
- オブジェクト参照の追跡
- メモリ使用状況のスナップショット取得
- 2つのスナップショット間の比較分析

**主要な処理内容**：
1. Luaレジストリからのオブジェクト走査
2. テーブル、関数、ユーザーデータ、スレッドの参照解析
3. メタテーブル、upvalue、ローカル変数の追跡
4. オブジェクト参照パスの特定
5. メモリレポートの生成

**関連システム・外部連携**：
- Luaデバッグライブラリ（debug.getinfo, debug.getupvalue等）
- Luaレジストリ
- 標準エラー出力（レポート出力）

**権限による制御**：特になし。開発者向けのデバッグ機能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はCLI/デバッグ用途で使用される内部機能 |

## 機能種別

デバッグ / メモリ診断

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| targets | any[] | No | 検索対象オブジェクト（catch関数） | - |
| flag | boolean | No | 設定フラグ（ignoreMainThread, enableCache） | - |
| old | report[] | Yes | 比較元レポート（compare関数） | - |
| new | report[] | Yes | 比較先レポート（compare関数） | - |

### 入力データソース

- Luaバーチャルマシンのレジストリ
- グローバルテーブル（_G）
- 基本型のメタテーブル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| snapshot | table | メモリスナップショットの内部データ構造 |
| paths | string[][] | オブジェクトへの参照パス（catch関数） |
| report | report[] | メモリレポート（report関数） |
| comparison | table | 2つのレポートの比較結果（compare関数） |

### 出力先

- 呼び出し元への戻り値
- ファイル出力（利用者が任意に指定）

## 処理フロー

### 処理シーケンス

```
1. スナップショット取得（snapshot）
   └─ レジストリからオブジェクト走査
2. テーブル解析（findTable）
   └─ キー、値、メタテーブルの参照を収集
3. 関数解析（findFunction）
   └─ upvalueの参照を収集
4. ユーザーデータ解析（findUserData）
   └─ uservalue、メタテーブルの参照を収集
5. スレッド解析（findThread）
   └─ スタック、ローカル変数の参照を収集
6. 参照パス検索（catch）
   └─ 指定オブジェクトへの参照パスを特定
7. レポート生成（report）
   └─ オブジェクト数と参照数を集計
```

### フローチャート

```mermaid
flowchart TD
    A[snapshot呼び出し] --> B[レジストリ取得]
    B --> C[find関数で再帰走査]
    C --> D{オブジェクト種別}
    D -->|table| E[findTable]
    D -->|function| F[findFunction]
    D -->|userdata| G[findUserData]
    D -->|thread| H[findThread]
    E --> I[キー・値・メタテーブル解析]
    F --> J[upvalue解析]
    G --> K[uservalue・メタテーブル解析]
    H --> L[スタック・ローカル変数解析]
    I --> M[参照情報を記録]
    J --> M
    K --> M
    L --> M
    M --> N[スナップショット完成]
    N --> O{目的}
    O -->|catch| P[参照パス検索]
    O -->|report| Q[レポート生成]
    O -->|compare| R[差分比較]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-80-01 | 主スレッド除外 | デフォルトでメインスレッドのスタックは走査しない | _ignoreMainThread=true |
| BR-80-02 | 弱参照テーブル | __mode='k'または'v'のテーブルは弱参照として扱う | メタテーブルに__modeあり |
| BR-80-03 | プライベート除外 | _privateテーブルに登録されたオブジェクトは走査対象外 | 内部オブジェクト |
| BR-80-04 | キャッシュ | enableCache時は最初のスナップショットを再利用 | キャッシュ有効時 |

### 計算ロジック

**オブジェクトポインタ取得**:
```lua
local function getPoint(obj)
    if hasPoint then
        return ('%p'):format(obj)  -- Lua 5.4以降
    else
        -- メタテーブルの__tostringを一時的に無効化
        local mt = getmetatable(obj)
        local ts = mt and rawget(mt, '__tostring')
        if ts then
            rawset(mt, '__tostring', nil)
        end
        local name = tostring(obj)
        if ts then
            rawset(mt, '__tostring', ts)
        end
        return name:match(': (.+)')
    end
end
```

**参照パス検索ロジック**:
```lua
function search(t)
    path[#path+1] = ('(%s)%s'):format(t.type, t.name)
    if targets[t.info.object] then
        push()  -- パスを結果に追加
    end
    for _, obj in ipairs(t.info) do
        search(obj)  -- 再帰的に検索
    end
    path[#path] = nil
end
```

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

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

本機能はデータベース操作を行わない。メモリ内オブジェクトの走査のみ。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | メタテーブルアクセスエラー | __tostring呼び出し失敗 | pcallで保護、nil返却 |
| - | upvalue取得失敗 | 無効なインデックス | ループ終了 |
| - | ローカル変数取得失敗 | 無効なインデックス | ループ終了 |

### リトライ仕様

リトライは行わない。エラー発生時は該当オブジェクトをスキップ。

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

トランザクション管理は不要。

## パフォーマンス要件

- 大規模なオブジェクトグラフの走査に対応
- キャッシュ機能で連続クエリを最適化
- 弱参照テーブルの適切な処理で不要なトラバースを回避

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

- デバッグライブラリ（debug.*）を使用するため、本番環境では無効化を推奨
- メモリ内の全オブジェクトにアクセス可能なため、機密情報の露出に注意

## 備考

- Lua 5.1から5.4まで対応（getuservalue/getfenvの互換性処理あり）
- %p書式指定子の有無を自動検出
- 複数のuservalue（Lua 5.4）に対応

---

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

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

### 推奨読解順序

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

Doctorモジュールの基本構造と内部状態を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | doctor.lua | `script/doctor.lua` | 1-28行目: デバッグライブラリのローカル化 |
| 1-2 | doctor.lua | `script/doctor.lua` | 151-166行目: _privateテーブルとprivate関数 |

**読解のコツ**: `_private`テーブルにはDoctor自身のオブジェクトが登録され、走査対象から除外される。

#### Step 2: オブジェクト表現を理解する

オブジェクトの文字列表現とフォーマット。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | doctor.lua | `script/doctor.lua` | 30-48行目: getPoint関数（ポインタ取得） |
| 2-2 | doctor.lua | `script/doctor.lua` | 85-149行目: formatName関数（オブジェクト名生成） |

**主要処理フロー**:
- **30-48行目**: オブジェクトのメモリアドレスを取得（Luaバージョン対応）
- **85-149行目**: 型別のオブジェクト名生成（nil, boolean, number, string, function, table, userdata）

#### Step 3: スナップショット取得を理解する

メモリスナップショットの取得処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | doctor.lua | `script/doctor.lua` | 171-471行目: snapshot関数 |
| 3-2 | doctor.lua | `script/doctor.lua` | 198-268行目: findTable関数 |
| 3-3 | doctor.lua | `script/doctor.lua` | 270-287行目: findFunction関数 |
| 3-4 | doctor.lua | `script/doctor.lua` | 289-318行目: findUserData関数 |
| 3-5 | doctor.lua | `script/doctor.lua` | 320-360行目: findThread関数 |

**主要処理フロー**:
- **198-212行目**: 弱参照テーブルの検出（__modeチェック）
- **213-258行目**: テーブルのキーと値を走査
- **270-287行目**: 関数のupvalueを走査
- **289-318行目**: ユーザーデータのuservalueとメタテーブルを走査
- **320-360行目**: コルーチンのスタックとローカル変数を走査

#### Step 4: 参照パス検索を理解する

特定オブジェクトへの参照パスの検索。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | doctor.lua | `script/doctor.lua` | 478-528行目: catch関数 |

**主要処理フロー**:
- **479-485行目**: 検索対象オブジェクトをtargetsテーブルに登録
- **499-523行目**: 再帰的にスナップショットを検索し、パスを記録

#### Step 5: レポート生成を理解する

メモリレポートの生成と比較。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | doctor.lua | `script/doctor.lua` | 535-573行目: report関数 |
| 5-2 | doctor.lua | `script/doctor.lua` | 583-598行目: compare関数 |

**主要処理フロー**:
- **540-565行目**: スナップショットを走査し、オブジェクトごとの参照カウントを集計
- **583-598行目**: 2つのレポートを比較し、共通オブジェクトの差分を抽出

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

```
m.snapshot() (メモリスナップショット取得)
    │
    ├─ find(registry) (レジストリから走査開始)
    │      │
    │      ├─ findTable(t)
    │      │      ├─ getmetatable() - 弱参照チェック
    │      │      ├─ next() - キー・値イテレート
    │      │      └─ find() - 再帰呼び出し
    │      │
    │      ├─ findFunction(f)
    │      │      └─ getupvalue() - upvalue取得
    │      │
    │      ├─ findUserData(u)
    │      │      ├─ getuservalue() - uservalue取得
    │      │      └─ getmetatable() - メタテーブル取得
    │      │
    │      └─ findThread(trd)
    │             ├─ getinfo() - スタックフレーム取得
    │             └─ getlocal() - ローカル変数取得
    │
    └─ 基本型メタテーブル走査

m.catch(...) (参照パス検索)
    │
    ├─ m.snapshot() - スナップショット取得
    │
    └─ search() - 再帰的パス検索
           └─ push() - パスを結果に追加

m.report() (レポート生成)
    │
    ├─ m.snapshot() - スナップショット取得
    │
    └─ scan() - オブジェクト集計
           └─ formatName() - オブジェクト名生成

m.compare(old, new) (レポート比較)
    └─ ポインタベースの差分抽出
```

### データフロー図

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

m.snapshot() ──────────▶ レジストリ走査 ─────────────▶ スナップショット
                              │                       (内部データ構造)
                              ▼
                        find() 再帰呼び出し
                              │
                              ├─ findTable
                              ├─ findFunction
                              ├─ findUserData
                              └─ findThread

m.catch(obj) ──────────▶ search() ──────────────────▶ 参照パス配列
                              │                       string[][]
                              ▼
                        パスを記録

m.report() ────────────▶ scan() ───────────────────▶ レポート配列
                              │                       report[]
                              ▼
                        オブジェクト集計

m.compare(old, new) ───▶ ポインタ比較 ─────────────▶ 差分結果
                                                    {old, new}[]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| doctor.lua | `script/doctor.lua` | ソース | メモリ診断のメインモジュール |
