Skip to content

Grepマルチスレッド対応・除外ファイル正規表現フィルタリング#2459

Open
hpmy-dev wants to merge 13 commits into
sakura-editor:masterfrom
hpmy-dev:feature/grepmatulithreads
Open

Grepマルチスレッド対応・除外ファイル正規表現フィルタリング#2459
hpmy-dev wants to merge 13 commits into
sakura-editor:masterfrom
hpmy-dev:feature/grepmatulithreads

Conversation

@hpmy-dev

Copy link
Copy Markdown

Grep機能拡張 仕様書

#2449 から一部修正、関係ないソースは除外し再度PR

1. 概要

サクラエディタのGrep機能に対し、以下2つの拡張を実装する。

  1. Grep処理のマルチスレッド化 — ファイル内検索を並列実行し、大量ファイルの検索を高速化する
  2. 除外ファイル指定の正規表現対応! プレフィックスで正規表現によるファイル除外を可能にする

2. 機能仕様

2.1 マルチスレッドGrep

2.1.1 対象

通常Grep(bGrepReplace=false)のみ。Grep置換は副作用(ファイル上書き)を伴うため従来の直列処理を維持する。

2.1.2 処理フロー

DoGrep()
  ├─ スレッドプール生成(1回のみ・nThreads個)
  │    各ワーカー: CBregexp/CSearchStringPattern をスレッドローカルに1回だけ初期化
  │
  ├─ バッチ0: 検索パス直下ファイル → DoGrepTreeEnumerate(bSubFolder=false) → RunBatch
  ├─ バッチ1: サブフォルダーA を再帰列挙 → RunBatch → vecTasks解放
  ├─ バッチ2: サブフォルダーB を再帰列挙 → RunBatch → vecTasks解放
  │   ...(1バッチ分のみメモリに保持、A→B→C の出力順序を保証)
  │
  └─ スレッドプール shutdown → join

サブフォルダー単位でバッチを分割することで、1バッチ分のみメモリに保持し、サブフォルダー順(A→B→C)の出力順序を保証する。

2.1.3 スレッド数

const int nIniThreads = GetDllShareData().m_Common.m_sSearch.m_nGrepThreadCount;
const unsigned int nClampedIni = (unsigned int)std::max(1, std::min(nIniThreads, 8));
const unsigned int nThreads = std::max<unsigned int>(nClampedIni, hardware_concurrency() / 4);
  • iniファイルの nGrepThreadCount(1〜8にクランプ)と 論理コア数 / 4 の大きい方を採用
  • デフォルト値: 2(iniファイル未設定時)
  • 上限: iniファイル設定値は8まで、hardware_concurrency() / 4 に上限なし

2.1.4 ini設定

[Common]
nGrepThreadCount=2
項目
セクション [Common]
キー nGrepThreadCount
int
範囲 1〜8(範囲外は自動クランプ)
デフォルト 2

注意: CShareData_IO.cppShareData_IO_Common() 関数内で pszSecName = L"Common" として common.m_sSearch.m_nGrepThreadCount を読み書きしているため、iniファイル上のセクションは [Common] です。

2.1.5 スレッドプール同期機構

変数 役割
poolBatchId size_t バッチ番号。インクリメントで新バッチを通知
bPoolShutdown bool true でワーカーに終了を指示
pPoolBatch const vector<SGrepFileTask>* 現在バッチのタスクリスト
poolNextTask atomic<size_t> 次に処理するタスクのインデックス(ロックフリー分配)
poolBatchActive atomic<int> 現在バッチを処理中のワーカー数
nActiveWorkers atomic<unsigned int> 初期化成功したワーカー数(デッドロック防止用)
bWorkCancelled atomic<bool> キャンセル済みフラグ(バッチ間で維持)
cvPoolStart condition_variable バッチ開始 / シャットダウン通知

2.1.6 排他制御

リソース 保護方法
ファイルタスク配布 atomic<size_t> poolNextTask(ロックフリー)
ヒット数 atomic<int> atomicHitCount
結果バッファ (sharedMessage) std::mutex resultMutex + std::lock_guard
フォルダーヘッダー重複排除 std::set<std::wstring>resultMutex で保護
AddTail() / BlockingHook() / IsCanceled() メインスレッドからのみ呼び出し

2.1.7 CBregexpスレッド安全性

CBregexp は内部状態を持つためスレッドセーフではない。各ワーカースレッドはスレッド生存期間中に1回だけ独自インスタンスを生成・コンパイルし、スレッド間で共有しない。

// ワーカースレッド起動時(1回のみ)
CBregexp localRegexp;
CSearchStringPattern localPattern;
localPattern.SetPattern(nullptr, searchKey.c_str(), searchKey.size(),
                        sSearchOption, &localRegexp);

2.1.8 キャンセル処理

  • メインスレッド: BlockingHook() / IsCanceled() で検出し bWorkCancelled.store(true, release)
  • ワーカースレッド: 32行ごとに bWorkCancelled.load(relaxed) を確認
  • RunBatchbool を返し、呼び出し元で nGrepTreeResult = -1 を設定(一元管理)

2.1.9 例外安全性

ワーカーラムダは try/catch(...) で囲み、poolBatchActive.fetch_sub(1)catch ブロックの外に配置。正常終了・例外発生どちらの経路でも必ずデクリメントされ、デッドロックを防止する。

ワーカー初期化(SetPattern)が例外で失敗した場合は nActiveWorkers をデクリメントし、RunBatch では nActiveWorkers の値を poolBatchActive の初期値に使用する。これにより、初期化失敗ワーカーが存在しても poolBatchActive が正確にゼロに到達し、メインスレッドのデッドロックを防止する。

// スレッドプール生成直後
std::atomic<unsigned int> nActiveWorkers{ nThreads };

// ワーカー初期化失敗時
catch(...){
    nActiveWorkers.fetch_sub( 1, std::memory_order_relaxed );
    // シャットダウンまで待機
}

// RunBatch内
poolBatchActive.store( (int)nActiveWorkers.load(std::memory_order_relaxed), ... );

2.1.10 出力順序

  • サブフォルダー間: 保証される(A→B→C の順にバッチ処理)
  • サブフォルダー内のファイル間: 実行のたびに変わる場合がある(並列処理のため)

2.2 除外ファイル指定の正規表現対応

2.2.1 構文

Grepダイアログのファイルパターン欄で ! プレフィックスを付けると、以降の文字列が正規表現パターンとして解釈される。

*.cpp;*.h;!.*\\bin\\[^.]+$;!.*\.bak$
プレフィックス 動作
なし ワイルドカード検索対象 *.cpp
# ワイルドカードフォルダー除外(従来機能) #.git
! 正規表現ファイル除外(新機能) !.*\.bak$

破壊的変更: 従来 ! はワイルドカード除外に使用されていた。新バージョンでは正規表現除外に変更される。旧形式(!*.bak 等)はGrep実行時に正規表現コンパイルエラーとなり、エラーダイアログでユーザーに通知される。サイレントな誤動作は発生しない。

2.2.2 マッチング対象

ファイルのフルパス全体に対して正規表現マッチングを行う。

パターン: !.*\\obj\\.*
マッチ対象: C:\project\src\obj\debug\file.obj  → 除外
マッチ対象: C:\project\src\main.cpp            → 対象

2.2.3 大文字小文字

大文字小文字を区別する(CBregexp::optCaseSensitive)。大文字小文字を無視したい場合は、正規表現内で (?i) を使用する。

2.2.4 入力バリデーション

Grep実行前にメインスレッドで全パターンの InitRegexp() + Compile() を事前検証する。無効なパターンはエラーダイアログ("無効な除外正規表現パターン: %s")を表示してGrep実行を中止する。

2.2.5 適用範囲

通常Grep・Grep置換の両方で有効。

処理 除外判定箇所
通常Grep(並列) ワーカースレッド内(スレッドローカルCBregexp使用)
Grep置換(直列) DoGrepTree() ファイルループ内

2.2.6 ヘッダー表示

Grep結果ウィンドウの「除外ファイル」行に正規表現パターンが表示される。

除外ファイル   .*\.bak$;.*\\obj\\.*

2.2.7 デフォルト除外パターン

CDlgGrep.hDEFAULT_EXCLUDE_FILE_PATTERN を正規表現形式に変更。

// 除外ファイルパターン(正規表現、フルパスに対してマッチング)
#define DEFAULT_EXCLUDE_FILE_PATTERN \
    L".*\.msi$;.*\.exe$;.*\.obj$;.*\.pdb$;.*\.ilk$;.*\.res$;.*\.pch$;.*\.iobj$;.*\.ipdb$"

3. アーキテクチャ

3.1 クラス・関数構成

CGrepAgent::DoGrep()
├─ [hWndTarget]     → DoGrepFile()(ウィンドウGrep・既存)
├─ [bGrepReplace]   → DoGrepTree() → DoGrepReplaceFile()(直列・既存)
└─ [通常Grep]       → 並列処理ブロック(新規)
    ├─ スレッドプール生成
    ├─ RunBatch() ラムダ
    │   ├─ ワーカー起床(condition_variable)
    │   ├─ メインスレッドUI監視ループ
    │   └─ 結果フラッシュ
    ├─ バッチ0: DoGrepTreeEnumerate(bSubFolder=false) → RunBatch
    ├─ バッチ1..N: CGrepEnumFilterFolders → DoGrepTreeEnumerate → RunBatch
    └─ スレッドプール shutdown

3.2 新規追加した構造体・関数

名前 種別 説明
SGrepFileTask 構造体 並列処理で使用するファイル情報(fullPath, fileName, baseFolder, folder, relPath)
DoGrepTreeEnumerate() メンバー関数 フォルダー走査のみ行い vecTasks にファイル情報を収集(メインスレッド用)
DoGrepFileWorker() メンバー関数 スレッドセーフなファイル内検索(UI更新なし・atomic cancelフラグ使用)

3.3 DoGrepTree の変更

  • nNest パラメータを削除(並列処理導入に伴いネスト管理が不要となったため)
  • std::vector<CBregexp>* pExclRegexps = nullptr 引数を追加。最上位呼び出しのみ関数内でコンパイルし、再帰呼び出しにはコンパイル済みポインタを引き継ぐ

4. 変更ファイル一覧

ファイル パス 変更内容
CGrepAgent.cpp sakura_core/agent/ 並列処理ブロック(スレッドプール+バッチ処理)、DoGrepTree 正規表現除外・nNest削除、DoGrepTreeEnumerate/DoGrepFileWorker 実装、デッドロック防止(nActiveWorkers
CGrepAgent.h sakura_core/agent/ SGrepFileTask 構造体、DoGrepTreeEnumerate/DoGrepFileWorker 宣言、DoGrepTree シグネチャ変更
CDlgGrep.h sakura_core/dlg/ DEFAULT_EXCLUDE_FILE_PATTERN を正規表現形式に変更
CGrepEnumKeys.h sakura_core/grep/ m_vecExceptFileRegexPatterns 追加、! プレフィックスパーサー変更、GetExcludeFiles() 戻り値型変更、ClearItems() クリア漏れ修正
CommonSetting.h sakura_core/env/ m_nGrepThreadCount メンバー追加
CShareData.cpp sakura_core/env/ m_nGrepThreadCount デフォルト値 2
CShareData_IO.cpp sakura_core/env/ nGrepThreadCount ini読み書き([Common]セクション)

注: CShareData_IO.cpp にGrep機能と無関係な std::size()std::extent_v の置換が1箇所含まれる(m_RegexKeywordList のサイズ取得)。


5. 制約・注意事項

項目 内容
Grep置換 直列処理を維持(データ競合防止)
フォルダー内出力順序 並列処理のため不定(サブフォルダー間の順序は保証)
フォルダー除外 # プレフィックスは従来のワイルドカード方式を維持
正規表現DLL ワーカースレッドでは InitRegexp(nullptr, ..., false) でUI表示なしにDLLをロード
std::filesystem 本改修では使用しない(ファイル走査はWin32 API FindFirstFile/FindNextFile ベース)
! プレフィックスの互換性 旧ワイルドカード形式は実行時エラーで検知される(サイレントな誤動作は発生しない)
スレッドセーフティ要確認 CDocTypeManager / GetDllShareData() のワーカースレッドからの読み取りアクセスが安全であることの継続的な検証が必要

関連
#2448
#2449

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Test Results

1 297 tests   1 296 ✅  6m 33s ⏱️
  114 suites      1 💤
    1 files        0 ❌

Results for commit d05711d.

♻️ This comment has been updated with latest results.

@hpmy-dev hpmy-dev force-pushed the feature/grepmatulithreads branch from efaa191 to d4727b4 Compare May 22, 2026 05:57
@berryzplus

Copy link
Copy Markdown
Contributor

マジメに設計しようとするとまず 「CGrepAgent::DoGrep の引数が多過ぎる」 で詰まると思うんですよね。

// Grep実行
DWORD DoGrep(
CEditView* pcViewDst,
bool bGrepReplace,
const CNativeW* pcmGrepKey,
const CNativeW* pcmGrepReplace,
const CNativeW* pcmGrepFile,
const CNativeW* pcmGrepFolder,
bool bGrepCurFolder,
BOOL bGrepSubFolder,
bool bGrepStdout,
bool bGrepHeader,
const SSearchOption& sSearchOption,
ECodeType nGrepCharSet, // 2002/09/21 Moca 文字コードセット選択
int nGrepOutputLineType,
int nGrepOutputStyle,
bool bGrepOutputFileOnly, //!< [in] ファイル毎最初のみ出力
bool bGrepOutputBaseFolder, //!< [in] ベースフォルダー表示
bool bGrepSeparateFolder, //!< [in] フォルダー毎に表示
bool bGrepPaste,
bool bGrepBackup
);

妥当なシグニチャは、たぶんこんな感じ。

	// Grep実行
	DWORD DoGrep(
		CEditView*				pcViewDst,
		CDlgGrep&				cDlgGrep,
		bool					bGrepHeader = true,
		bool					bGrepStdout = false,
		bool					bGrepCurFolder = false
	);

「メソッド引数を何個まで許容するか?」の指標によく使われるのは「7±2」の規則。
デザインパターン「パラメーターオブジェクト」の活用には弊害もあるので「やろう」とは言いづらいけど、これくらいの文明化はしてもいいんじゃないかと思ったり。

※サクラエディターのソースコードにはパラメーターオブジェクトの適用失敗例も含まれています。


「Grep実行のオプション」を格納する構造体が5つ以上あるのも変な感じ。

  • SGrepInfo コマンドラインオプション。おそらく、これが本体。
  • CDlgGrep Grepダイアログ。メンバー変数名が違うけどSGrepInfoと同じ。
  • SGrepOption CGrepAgent内でオプションを格納するための入れ物。
  • CDlgGrepReplace Grep置換ダイアログ。CDlgGrepを継承してるけど、置換テキストだけ独自に持っている。
  • CommonSetting_Search 共有メモリ構造体の「検索」部分。これは「本体」のコピーにあたるけど、必要。

対応するとコード行数がガッツリ減ってカバレッジが下がるので長いこと放置中。


Grepに関して、思っていること。

  • テキスト検索・置換の拡張機能なのに、独自に検索処理を実装してるの、おかしいですよね?

CSearchAgentのロジックを流用できないことは分かっていますが、「CSearchAgent の実装がおかしい、からGrepに流用できない」という見方もできると思うわけで。

@hpmy-dev

hpmy-dev commented May 28, 2026

Copy link
Copy Markdown
Author

確かに構造体が複数あるのはイビツですね。

ただ本PRではDoGrepの既存シグネチャをそのまま使っていますが、
マルチスレッドのコア実装(スレッドプール、同期機構、ワーカー処理)は
DoGrepの引数の渡し方とは独立しています。

シグネチャ整理が先でも後でも、マルチスレッド部分の手戻りは
ほぼ発生しないため、順序に依存関係はないと考えています。
本PRのスコープとは独立した改善テーマになるかと思います

本PRでは既存のインターフェースを変更せずにマルチスレッド化を導入する方針としていますが、
もしシグネチャ整理を先にやるべきというご意見であれば、別issueで議論いただけると助かります。

高速化の要望も過去にありますし、本PRの実装内容(スレッドプールの同期機構、排他制御、
CBregexpのスレッド安全性、キャンセル処理など)について、何かご懸念やフィードバックをお願いします。

#857

@berryzplus

Copy link
Copy Markdown
Contributor

レビューに関しては、SonarQubeの解析結果がNGなので、よほどの理由がなければ「マージ前提」で見るつもりはありません。(出していただいたPull Requestは参考にはします。)

image
  • 「セキュリティ的に不安」のカテゴリで指摘がある。
  • 修正コードに対するテストカバレッジが8割を満たしていない。
  • 「保守性に関して将来不安」のカテゴリの指摘がやや多い。

セキュリティの指摘は wcscpy と wcslen なので、対象行を触っていなければスルーでも良いです。Grep機能がもともとテスト不能なのは分かってますが、「一部できるように改善しました」で5%を切るのはない気がしました。元コードが酷いので「保守性に関する指摘」はスルーでよいかもしれません。

@hpmy-dev

hpmy-dev commented Jun 1, 2026

Copy link
Copy Markdown
Author

ご確認ありがとうございます。
SonarQube が NG のままマージ前提では見ない、という方針は理解しました。
実装と仕様の参照元として整理しておきます。参考にしていただければ十分です。

以前の対応の切り出しと追加PRで補えていると考えています。

ご指摘の3点について、こちらの認識です。

カバレッジ
「一部テストできるように改善した」と言いながら 5% を切るのはない、というのはその通りです。
本PR単体で 4.5% に見えるのは、対象 .cpp の新規行に対するテストが #2460 側に乗っているためで、CGrepAgent.cpp の追加分をテスト可能な単位に分解した上での検証は #2460 にまとめています。
テスト自体は #2460 も合わせてご参照頂ければと思います。

セキュリティ(wcscpy / wcslen)
定型的な指摘ですが、これらは新規に書いたものではなく既存 DoGrepFile のロジックを DoGrepFileWorker に複製した箇所です(挙動は不変、szCpName[100] に対し代入元は短い固定文字列)。
diff 上は追加行なので集計には乗りますが、内容は既存同等です。
複製元と挙動を揃える観点から、安全側コメントを付けて Safe マークでの対応を想定しています。
_s 系への置き換えが望ましければそちらで対応します。

保守性
元コードの事情を踏まえスルーで可、という方針は承知しました。
繰り返しになりますが、テスト容易化に必要な範囲の構造改善は #2460 に含めています。

引き続き、実装・テストの参照用として活用いただければ幸いです。

@hpmy-dev hpmy-dev force-pushed the feature/grepmatulithreads branch 4 times, most recently from 3dac619 to d4727b4 Compare June 5, 2026 09:35
@hpmy-dev

hpmy-dev commented Jun 11, 2026

Copy link
Copy Markdown
Author

grep 関連の機能リファクタリングとテスト強化

#2460 カバレッジ対応としてマージ。

概要

CGrepAgent / CDlgGrep に対するリファクタリングと、Grep 機能の回帰テストを追加

変更規模: 11 ファイル / テストコード約 2,500 行・138 テスト追加

関連

#2439
#2459
#1689

主な変更内容

  1. 並列 Grep 実装: Producer-Consumer パターンによるサブフォルダー単位バッチ処理(CGrepAgent::DoGrep
  2. static ヘルパ切り出し: FormatGrepResultLine / BuildGrepHeader / BuildGrepFooter(テスト容易化)
  3. CDlgGrep API 整理: DetermineDefaultExcludePatterns / BuildHwndFileToken / IsHwndFileToken / GetPackedGFileString*.* 補完 / 自テキスト検索時の履歴非汚染
  4. テスト 5 ファイル: 新規 3 + 既存改修 2

リファクタリングの目的とテスト実行方法

プロダクトコードから HWND 非依存の純粋ロジックを static 関数として切り出すことで、エディタ全体を起動せずにユニットテストから直接検証可能にした。以下のコマンドで機能単位の確認ができる。

CGrepAgent 切り出し関数

FormatGrepResultLine / BuildGrepHeader / BuildGrepFooter / CreateFolders / ChopYen / AddTail / OnBeforeClose

tests1.exe --gtest_filter=CGrepAgentTest.*:CGrepAgent.*:GrepAgentFlowTest.*

CDlgGrep 切り出し関数

DetermineDefaultExcludePatterns / BuildHwndFileToken / IsHwndFileToken / GetPackedGFileString

tests1.exe --gtest_filter=CDlgGrepTest.*

DoGrepFileWorker(既存 + 異常系)

tests1.exe --gtest_filter=GrepRealFileTest.*:GrepIrregularTest.*

全件

tests1.exe

継続課題

test-grep-irregular.cpp ソース

IRR-16: 長大コマンドライン引数テスト(コメントアウト中)

DEBUG_TRACEDebugOutW(バッファサイズ 16,000 文字)でバッファ溢れが発生し、Debug ビルドで ::DebugBreak() によるクラッシュが起きる。-GKEY 以外の全オプション引数(-GREPR-GFILE-GFOLDER 等)に対しても CCommandLine::CheckCommandLine で文字数制限をかけた後に復活
テストで発覚したので動確は出来てそう。


変更ファイル一覧

変更ファイル一覧(11 ファイル)
# ファイル 種別 変更内容
1 sakura_core/agent/CGrepAgent.h 改修 SGrepFileTask、static ヘルパ 3 関数、DoGrepFileWorker / DoGrepTreeEnumerate 宣言
2 sakura_core/agent/CGrepAgent.cpp 改修 並列 Grep 本体、static ヘルパ実装、DoGrepFileWorker / DoGrepTreeEnumerate 実装
3 sakura_core/dlg/CDlgGrep.h 改修 DetermineDefaultExcludePatterns / BuildHwndFileToken / IsHwndFileToken 宣言
4 sakura_core/dlg/CDlgGrep.cpp 改修 上記 3 関数実装、GetPackedGFileString *.* 補完、履歴汚染防止
5 sakura_core/tests1.vcxproj 改修 テスト 4 ファイル追加
6 sakura_core/tests1.vcxproj.filters 改修 テスト 4 ファイル追加
7 src/test/cpp/tests1/test-ccommandline.cpp 改修 既存コメント体裁統一 + Phase 2-A 25 テスト追加
8 src/test/cpp/tests1/test-cdlggrep.cpp 新規 26 テスト
9 src/test/cpp/tests1/test-cgrepagent-flow.cpp 新規 20 テスト
10 src/test/cpp/tests1/test-grep-irregular.cpp 新規 46 テスト(+ IRR-16 コメントアウト 1)
11 src/test/cpp/tests1/test-grep.cpp 新規 41 テスト

テスト一覧(ソース出現順)

📂 test-grep.cpp(41 テスト)

既存相当(20 テスト)

# スイート テスト名
1 GrepSearchEngine LiteralSearchCaseSensitivity
2 GrepRealFileTest JapaneseLiteralSearchAcrossMixedEncodings
3 GrepRealFileTest RegexCompileAndMatch
4 CGrepEnumKeys ParseRegexExcludePattern
5 CGrepEnumKeys ParseFileAndFolderKeysWithDefaults
6 CGrepEnumKeys AbsolutePathInSearchKeyIsRejected
7 CCodeMediator AutoDetectBomAndDefaultCode
8 GrepRealFileTest EnumerateFiles_FiltersByExtensionAndExcludeKey
9 GrepRealFileTest EnumerateFolders_ExcludesByHashKey
10 GrepRealFileTest EnumerateFiles_SubfolderRecursion
11 GrepRealFileTest FileWorker_BasicLiteralHitsInUtf8File
12 GrepRealFileTest FileWorker_CaseSensitivityIsRespected
13 GrepRealFileTest FileWorker_RegexHitsInUtf8File
14 GrepRealFileTest FileWorker_JapaneseAcrossEncodings
15 GrepRealFileTest FileWorker_32000LinesIsSearchable
16 GrepRealFileTest FileWorker_CancelTerminatesScan
17 GrepRealFileTest MultiThread_HitCountMatchesSingleThread
18 GrepRealFileTest MultiThread_StressNoDeadlockAcrossRepeats
19 GrepRealFileTest RegexExclude_AppliedToEnumeratedFiles
20 GrepRealFileTest RegexExclude_InvalidPatternFailsToCompile

Phase 2-B 追加(21 テスト)

# テスト名 検証観点
21 SplitPattern_SemicolonSeparator ; 区切り
22 SplitPattern_SpaceSeparator スペース区切り
23 SplitPattern_CommaSeparator , 区切り
24 SplitPattern_MixedSeparators 3 種混在
25 SplitPattern_ConsecutiveSeparators 連続区切り
26 SplitPattern_TrailingSeparator 末尾区切り
27 SplitPattern_QuotesRemoved クォート除去
28 SplitPattern_QuotesInMiddle 途中クォート
29 SetFileKeys_EmptyInputFallsBackToWildcard 空入力 → *.*
30 SetFileKeys_WildcardInPathReturns1 パス中ワイルドカード
31 SetFileKeys_AbsolutePathInSearchReturns2 絶対パス
32 SetFileKeys_ExcludeFolderHashRelative # 相対除外
33 SetFileKeys_ExcludeFolderHashAbsolute # 絶対除外
34 SetFileKeys_RegexExcludeNoValidation ! バリデーションスキップ
35 SetFileKeys_DuplicateKeyDeduplicated 重複排除
36 AddExceptFile_RelativePath 相対除外ファイル
37 AddExceptFile_AbsolutePath 絶対除外ファイル
38 AddExceptFolder_DefaultPattern 既定除外フォルダー
39 AddExceptFile_HashIsNotSpecial # 非特殊
40 GetExcludeFiles_MergesAllThreeArrays 3 配列集約
41 GetExcludeFolders_MergesRelAndAbs 相対+絶対集約
📂 test-cgrepagent-flow.cpp(20 テスト)
ID テスト名 検証観点
GA-01 FormatGrepResultLine_NormalStyle1_ProducesPathLineColContent Normal + 該当行
GA-02 FormatGrepResultLine_WzStyle2_FileGrouped WZ 風
GA-03 FormatGrepResultLine_ResultOnlyStyle3_NoPath 結果のみ
GA-04 FormatGrepResultLine_OutputLineType0_OnlyMatchPart 該当部分のみ
GA-05 FormatGrepResultLine_OutputLineType1_FullLine 行全体
GA-06 FormatGrepResultLine_OutputLineType2_NegativeLine 否該当行
GA-07 BuildGrepHeader_BasicShape 基本要素
GA-08 BuildGrepHeader_WithRegexOption_IncludesMarker 正規表現マーカー
GA-09 BuildGrepHeader_WithCaseSensitive_IncludesMarker 大文字小文字マーカー
GA-10 BuildGrepFooter_ZeroHits_HasZeroMessage 0 件
GA-11 BuildGrepFooter_PositiveHits_HasCount 42 件(通常/置換)
GA-12 CreateFolders_SemicolonSeparated_SplitsCorrectly ; 分割
GA-13 CreateFolders_QuotedSemicolon_PreservesAsOne クォート内 ;
GA-14 CreateFolders_LongFileName_Resolved 8.3 → ロングパス
GA-15 ChopYen_LastBackslashRemoved 末尾 \ 除去
GA-16 ChopYen_NoTrailingYen_Unchanged 無変更
GA-17 ChopYen_RootPath_AlsoTrimmed C:\C:
GA-18 AddTail_StdoutMode_WritesToStdout stdout 書き込み
GA-19 OnBeforeClose_DuringGrepReturnsInterrupt Grep 中割り込み
GA-20 OnBeforeClose_NotRunningReturnsContinue 停止中継続
📂 test-cdlggrep.cpp(26 テスト / DG-19 欠番)
ID テスト名 検証観点
DG-01 DefaultMemberValues_Constructor 初期値
DG-02 DefaultExcludePatterns_Constants 定数値
DG-03 GetPackedGFileString_NoExclusions 除外なし
DG-04 GetPackedGFileString_WithExcludeFolders # 付加
DG-05 GetPackedGFileString_WithExcludeFiles ! 付加・*.* 補完
DG-06 GetPackedGFileString_EscapeBangInExcludeFolder ! エスケープ
DG-07 GetPackedGFileString_EscapeHashInExcludeFolder # エスケープ
DG-08 GetPackedGFileString_EscapeSpaceInExcludeFolder スペース分割
DG-09 GetPackedGFileString_EscapeSemicolonInExcludeFolder セミコロン分割
DG-10 GetPackedGFileString_CombinedAllExclusions 複合
DG-11 RoundTrip_GuiToCliToEnumKeys Pack→Parse 往復
DG-12 DetermineDefaultExcludePatterns_EmptyHistorySetsDefaults 履歴空
DG-13 DetermineDefaultExcludePatterns_NonEmptyHistoryUsesHistoryTop 履歴先頭
DG-14 DetermineDefaultExcludePatterns_AlreadySetSkipped 設定済みスキップ
DG-15 BuildHwndFileToken_Format HWND 文字列化
DG-16 BuildHwndFileToken_NullHwnd NULL ゼロ埋め
DG-17 IsHwndFileToken_PositiveCases 正常 → true
DG-18 IsHwndFileToken_NegativeCases 異常 → false
DG-20 DoModalCancelImmediately_NoException IDCANCEL → rc=0
DG-21 DoModalOK_WithValidInputs_ReturnsOK 有効入力 → rc=1
DG-22 DoModalOK_EmptyFolder_FallsBackToCurrentDir 空フォルダー
DG-23 DoModalOK_InvalidRegex_StillReturnsOK 不正正規表現
DG-24 DoModalOK_FolderWithSemicolon_IsSplitBySeparator セミコロン区切り
DG-25 DoModalOK_MultipleFolders_AllResolved 複数絶対パス
DG-26 DoModalOK_HistoryRecordedExceptFromThisText 履歴追加
DG-27 DoModalOK_FromThisText_DoesNotPolluteHistory 履歴非汚染
📂 test-grep-irregular.cpp(46 テスト + IRR-16 コメントアウト 1)

CGrepEnumKeys 境界(IRR-01〜IRR-08 + IRR-07b)

ID テスト名
IRR-01 SetFileKeys_EmptyString_DefaultsApplied
IRR-02 SetFileKeys_OnlyWhitespace_DefaultsApplied
IRR-03 SetFileKeys_OnlyExcludePatterns_DefaultSearchApplied
IRR-04 SetFileKeys_VeryLongPattern_4096Chars
IRR-05 SetFileKeys_ManyDuplicates_DeduplicatedTo1
IRR-06 SetFileKeys_NullByteInMiddle_TruncatesAtNull
IRR-07 SetFileKeys_BangPrefixWithInvalidRegex_RegisteredButFailsLater
IRR-07b Regex_InvalidExcludePatternFailsToCompile
IRR-08 SetFileKeys_PathWithDriveLetterInMiddle_TreatedAsRelative

CCommandLine 境界(IRR-09〜IRR-19)

ID テスト名
IRR-09 CommandLine_GKEY_EmptyValue_Ignored
IRR-10 CommandLine_GREPR_EmptyValue_AcceptedAsEmpty
IRR-11 CommandLine_GFOLDER_NonExistentPath_StoredAsIs
IRR-12 CommandLine_GCODE_OutOfRange_StoredAsIs
IRR-13 CommandLine_GCODE_Negative_StoredAsIs
IRR-14 CommandLine_GKEY_WithControlChars_Stored
IRR-15 CommandLine_GKEY_WithSurrogatePair
IRR-16 CommandLine_TotalLength_NearWindowsLimit_32767Chars(コメントアウト・継続課題)
IRR-17 CommandLine_ResponseFileWithGrepArgs_Parsed
IRR-18 CommandLine_DoubleDashStopsOptionParsing
IRR-19 CommandLine_ColonSeparatorEquivalentToEqual

除外正規表現コンパイル検証(IRR-20〜IRR-25)

ID テスト名
IRR-20 ExcludeFile_ValidRegex_CompilesSuccessfully
IRR-21 ExcludeFile_GlobPattern_CompileFails
IRR-22 ExcludeFile_EmptyPattern_NoCompileNoError
IRR-23 ExcludeFile_MixedRegexAndGlob_FirstSucceedsSecondFails
IRR-24 ExcludeFile_MixedGlobAndRegex_OrderReversed
IRR-25 ExcludeFile_AllValidRegex_AllCompileSuccessfully

DoGrepFileWorker 境界(IRR-26〜IRR-34)

ID テスト名
IRR-26 FileWorker_ZeroByteFile_NoHit
IRR-27 FileWorker_OnlyBomFile_NoHit
IRR-28 FileWorker_InvalidUtf8Sequence_DoesNotCrash
IRR-29 FileWorker_FileWithoutFinalNewline_LastLineSearched
IRR-30 FileWorker_MixedLineEndings_LineNumbersConsistent
IRR-31 FileWorker_VeryLongSingleLine_64KChars
IRR-32 FileWorker_LockedFile_ReturnsError
IRR-33 FileWorker_ReadOnlyAttribute_Searched
IRR-34 FileWorker_HiddenSystemFile_Searched

CCodeMediator 境界(IRR-35〜IRR-38)

ID テスト名
IRR-35 CodeMediator_ShortFile_FallbackToDefault
IRR-36 CodeMediator_SjisSecondByteInAsciiRange_NotMisdetected
IRR-37 CodeMediator_PriorCesu8Flag_TogglesDetection
IRR-38 CodeMediator_Utf32LeBom_NotMisdetectedAsUtf16

正規表現・並列処理(IRR-39〜IRR-46)

ID テスト名
IRR-39 Regex_EmptyPattern_CompileFailsOrEmptyMatch
IRR-40 Regex_DotStar_DoesNotHang
IRR-41 Regex_CatastrophicBacktracking_TimeoutGuarded
IRR-42 MultiThread_CancelImmediatelyAfterStart
IRR-43 MultiThread_FileDeletedMidScan
IRR-44 MultiThread_ZeroExcludeRegexes_Equivalent
IRR-45 MultiThread_100ExcludeRegexes_AllApplied
IRR-46 MultiThread_RepeatedStartCancel_50Iterations
📂 test-ccommandline.cpp Phase 2-A(25 テスト追加)

-GOPT 複合・境界値(9 テスト)

# テスト名
1 ParseGrepOpt_MultipleFlagsCombined
2 ParseGrepOpt_AllKnownFlagsOn
3 ParseGrepOpt_DuplicateSameFlag
4 ParseGrepOpt_StyleLastWins
5 ParseGrepOpt_OutputLineTypeLastWins
6 ParseGrepOpt_UnknownCharIgnored
7 ParseGrepOpt_EmptyValueRejected
8 ParseGrepOpt_MixedDigitsAndLetters

-GREPR / -GKEY 連動(4 テスト)

# テスト名
9 ParseGrepReplace_GREPRSetsReplaceFlag
10 ParseGrepReplace_OnlyGKEYDoesNotSetReplaceFlag
11 ParseGrepReplace_GREPREmptyAllowed
12 ParseGrepReplace_ClipboardPasteFlag

Case-insensitive・引数順序(5 テスト)

# テスト名
13 ParseGrep_OptionNameLowerCase
14 ParseGrep_OptionNameMixedCase
15 ParseGrep_ArgumentOrderInvariant
16 ParseGrep_GrepModeAndGrepDlgBothSpecified
17 ParseGrep_GKeyDuplicate_LaterWins

-GKEY / -GFILE / -GFOLDER 値境界(8 テスト)

# テスト名
18 ParseGrepKey_SingleChar_Boundary
19 ParseGrepKey_VeryLongValue_4096Chars
20 ParseGrepKey_JapaneseCharsAndEmoji
21 ParseGrepKey_ContainsEqualsInValue
22 ParseGrepKey_EscapedDoubleQuotes
23 ParseGrepFile_RegexExcludePatternFromPR2449
24 ParseGrepFolder_RelativePath
25 ParseGrepFolder_UncPath

テスト数サマリー

ファイル テスト数
test-grep.cpp 41
test-cgrepagent-flow.cpp 20
test-cdlggrep.cpp 26
test-grep-irregular.cpp 46(+ IRR-16 コメントアウト 1)
test-ccommandline.cpp Phase 2-A 25
合計 158 定義 / 138 新規追加

ライセンス

Zlib License (SPDX-License-Identifier: Zlib)

@hpmy-dev

Copy link
Copy Markdown
Author

Grep並列化: RunParallelGrep 切り出し・デッドロック対策・カバレッジ追加 + SonarQube セキュリティ Hotspot 対応

概要

CGrepAgent::DoGrep() 内にインラインで埋め込まれていた並列 Grep のオーケストレーション(スレッドプール生成・ワーカー駆動・バッチ処理)を、テストから直接駆動できるメンバ関数 RunParallelGrep として切り出す。あわせてワーカー初期化失敗時のデッドロックを解消し、RunParallelGrep 本体を通すユニットテストを追加して新規コードカバレッジを引き上げる。さらに、新規コードで SonarQube が指摘したセキュリティ Hotspot(wcslen / wcscpy)を安全な実装へ置換して解消する。

本コミット(68df47dd8)は #2459 の追加対応であり、既存の並列 Grep の挙動は変えない。

関連

主な変更内容

  1. RunParallelGrep の切り出し: DoGrep() 内の並列ブロック(スレッドプール管理・バッチ駆動コア)を CGrepAgent::RunParallelGrep として分離。pcViewDst / pcDlgCancelnullptr 可(nullptr 時は UI 操作をスキップ)とし、UI 非依存でテストから生成・駆動できるようにした。戻り値は -1=キャンセル / 0=完了、合計ヒット数は出力引数で返す。
  2. デッドロック対策: ワーカー初期化失敗(検索パターンのコンパイル例外など)時に、バッチ駆動側が稼働ワーカー数を減算前に読むことで poolBatchActive が 0 に到達せずメインループがハングし得る問題を解消。全ワーカーの初期化完了(成功/失敗)を待ってからバッチ駆動に入る/実参加ワーカー数で数えるように修正。
  3. カバレッジ追加: RunParallelGrep 本体の主要分岐(サブフォルダー単位バッチ、ヘッダー出力、除外マッチ、結果集約、キャンセル)を直接踏むテストを test-grep.cpp に追加。並列とシングルスレッドのヒット一致・連続 start/cancel のストレス・初期化失敗時の非ハングなどを検証する。
  4. SonarQube セキュリティ Hotspot 対応: 新規コードの wcslen(5 箇所)を std::wstring_view(...).length() に、DoGrepFileWorkerwcscpy(1 箇所)を wcscpy_s(...) に置換し、#include <string_view> を追加。挙動は元と同一で、上限指定による切り詰めも発生しない。

SonarQube セキュリティ Hotspot の修正内容(CGrepAgent.cpp 新規コード)

関数 変更前 変更後
DoGrepTreeEnumerate int(wcslen(pszBasePath)) int(std::wstring_view(pszBasePath).length())
DoGrepTreeEnumerate (int)wcslen(pszPath) (int)std::wstring_view(pszPath).length()
DoGrepFileWorker int(wcslen(pszKey)) int(std::wstring_view(pszKey).length())
DoGrepFileWorker task.fullPath.size() + wcslen(pszCodeName) + 10 task.fullPath.size() + std::wstring_view(pszCodeName).length() + 10
BuildGrepHeader (int)wcslen(pszKey) (int)std::wstring_view(pszKey).length()
DoGrepFileWorker wcscpy( szCpName, CCodeTypeName(nCharCode).Bracket() ) wcscpy_s( szCpName, std::size(szCpName), CCodeTypeName(nCharCode).Bracket() )

加えて #include <string_view> を追加(#include <set> の直後)。

補足

  • wcslen 置換は NUL 終端文字列の長さ取得で、std::wstring_view のコンストラクタが内部で長さを計算するため戻り値・挙動は不変。外側の int(...) / (int) キャストは維持。
  • wcscpy 置換のコピー先 szCpNameWCHAR[100]、コピー元 CCodeTypeName::Bracket() は短い固定文字列でオーバーランは発生しない。コピー先要素数を渡す wcscpy_s で Hotspot を解消。
  • 既存関数 DoGrepTree / DoGrepFile / DoGrepReplaceFilewcslen / wcscpy は新規コードの対象行ではないため変更しない(CLAUDE.md の「動いている既存行は触らない」方針)。

変更ファイル一覧(3本)

# ファイル 種別 変更規模 変更内容
1 sakura_core/agent/CGrepAgent.cpp 改修 958 行 DoGrep() 内並列オーケストレーションを RunParallelGrep へ切り出し。ワーカー初期化失敗時のデッドロック対策。新規コードの wcslen×5 を std::wstring_view().length()wcscpy×1 を wcscpy_s に置換、#include <string_view> 追加(SonarQube セキュリティ Hotspot 対応)
2 sakura_core/agent/CGrepAgent.h 改修 93 行 RunParallelGrep(スレッドプール管理・バッチ駆動コア)の宣言追加。pcViewDst/pcDlgCancel は nullptr 可。
3 src/test/cpp/tests1/test-grep.cpp 追加 1435 行 RunParallelGrep 本体の主要分岐・デッドロック非再現を直接検証するテストを追加。

検証

  • ローカルビルドは行わない。差分はこの3ファイルに限定され、既存の直列パス(DoGrepFile / DoGrepReplaceFile)には波及しない。
  • 追加テストで RunParallelGrep の主要分岐が covered になること、初期化失敗時にハングしないことを確認する。
  • SonarQube セキュリティ Hotspot(wcslen / wcscpy)が Resolved になること、新規コードカバレッジ・Quality Gate を CI / SonarCloud の再スキャンで確認する。

ライセンス

Zlib License (SPDX-License-Identifier: Zlib)

@hpmy-dev hpmy-dev force-pushed the feature/grepmatulithreads branch 3 times, most recently from b0afc7e to d6a43bc Compare June 12, 2026 07:36
@hpmy-dev hpmy-dev force-pushed the feature/grepmatulithreads branch from d6a43bc to 5461b02 Compare June 12, 2026 07:58
@hpmy-dev

Copy link
Copy Markdown
Author

SonarQube Security Hotspot 対応: 新規コードの wcscpy / wcslen を境界付き操作へ置換

概要

PR #2459 の SonarCloud 解析で報告された Security Hotspot(Buffer Overflow カテゴリ 6 件: wcscpy ×1 / wcslen ×5)を解消する。

変更規模: 2 ファイル / +8 -7(プロダクトコードのみ・機能変更なし)

関連

#2459
https://sonarcloud.io/project/security_hotspots?sinceLeakPeriod=true&pullRequest=2459&id=sakura-editor_sakura

主な変更内容

  1. wcscpyStaticString::assign(): CDlgGrep::GetData() の HWND トークン代入。assign() はバッファ超過時に切り詰めて NUL 終端を保証する既存 API
  2. wcslenstd::wstring_view::length(): 新規切り出し関数(DoGrepTreeEnumerate / DoGrepFileWorker / BuildGrepHeader)内の長さ取得 5 箇所。入力はいずれも std::wstring::c_str() 由来等で NUL 終端が保証されており実害はないが、境界契約を型で明示する
  3. assertwcslenwcsnlen_s: BuildGrepFooter の言語リソース長検証。読み取り上限を kGrepFooterBufSize に制限(リソースが上限以上の場合も assert は意図どおり失敗する)

既存コードの移動分(CDlgGrep.cppOnBnClickedwcscpy 等)は SonarCloud 上で既存 Hotspot として追跡されるため、本 PR のスコープ外とした。

変更ファイル一覧

# ファイル 種別 変更内容
1 sakura_core/agent/CGrepAgent.cpp 改修 wcslen 6 箇所置換(うち 1 箇所は assert 内 wcsnlen_s 化)、<string_view> 明示インクルード
2 sakura_core/dlg/CDlgGrep.cpp 改修 wcscpy 1 箇所を StaticString::assign() へ置換

テスト

機能変更がないため、既存の Grep 回帰テストでカバーする。

tests1.exe --gtest_filter=CGrepAgentTest.*:CGrepAgent.*:GrepAgentFlowTest.*:CDlgGrepTest.*:GrepRealFileTest.*:GrepIrregularTest.*
  • DG-15〜DG-18(BuildHwndFileToken / IsHwndFileToken)が CDlgGrep 側変更の直接の回帰テスト
  • GA-07〜GA-11(BuildGrepHeader / BuildGrepFooter)がヘッダ/フッタ生成の回帰テスト

マージ後、SonarCloud の PR 解析で Security Hotspots(New Code)が 0 件になることを確認する。

ライセンス

Zlib License (SPDX-License-Identifier: Zlib)

@hpmy-dev

Copy link
Copy Markdown
Author

Grep新規コードのテストカバレッジ追加

#2459 のマルチスレッドGrep・除外ファイル正規表現フィルタリングに対し、SonarCloud の New Code カバレッジを満たすためのテストを追加する。

関連: #2459 #2460

1. 概要

#2459 で追加した CGrepAgent の新規コード(並列処理ブロック・DoGrepFileWorker・ヘッダー/フッター生成・除外正規表現適用)は、従来 UI 依存のため単体テスト不能とされていた箇所が多く、SonarCloud の New Code カバレッジが基準(80%)を大きく下回っていた。

本PRは テストコードのみ を追加し、プロダクションコード(CGrepAgent.cpp / CGrepAgent.h 等)には一切変更を加えずに、これらの新規ロジックを実ファイルシステム経由で検証してカバレッジを引き上げる。

2. 方針

  • テストのみ: src/test/cpp/tests1/test-grep.cpp への追加に限定。プロダクションコードは無変更。
  • 実ファイル経由: 一時ディレクトリ(RAII クラス GrepTempDir)に実ファイルを生成し、DoGrepFileWorker / RunParallelGrep / DoGrep を実際に走らせて挙動を検証する。
  • UI 非依存経路を直接呼ぶ: pcDlgCancel / pcViewDstnullptr を渡してスレッドプール経路を駆動。DoGrep 本体は EditorTestSuite フィクスチャと stdout モード(bGrepStdout=true)で headless 実行する。
  • SonarCloud セキュリティ非抵触: 追加コードに wcslen / wcscpy 等の指摘対象 API を使用しない(std::wstring_view / RAII で代替)。

3. 追加テスト

test-grep.cpp 末尾にセクション 13〜15 を追加。

セクション 対象 主な検証内容
13 DoGrepFileWorker / FormatGrepResultLine / BuildGrepHeader FileOpen 失敗時の CError_FileOpen 捕捉、事前キャンセルの -1 復帰、空キー時の出力スタイル書式( / / / 装飾なし)、行 2000 文字・マッチ 2500 文字のクランプと EOL 付加、nullptr 引数の既定値処理
14 else / catch 分岐 自動判定での (DetectError) 出力、正規表現ゼロ長マッチの matchlen 補正による無限ループ回避、大文字小文字区別のヘッダー切り替え、lineType=0(一致箇所)と lineType=1(一致行)のヘッダー差分
15 DoGrep 本体スモーク EditorTestSuite + stdout 経路で DoGrep を headless 実行。ヘッダー組み立て・! 除外正規表現の事前検証 → 適用 → RunParallelGrep → フッターまでの全経路を駆動

4. 変更ファイル一覧

ファイル パス 変更内容
test-grep.cpp src/test/cpp/tests1/ セクション 13〜15 のテスト追加(プロダクションコード変更なし)

5. テスト結果(ローカル)

.\Win32\Release\tests1.exe --gtest_filter=GrepRealFileTest.*
[==========] 52 tests from 1 test suite ran. (1638 ms total)
[  PASSED  ] 52 tests.

DoGrep_StdoutMode_* を含む全件がローカルの Release ビルドで PASS(合計 1.6 秒)。

6. 制約・注意事項

項目 内容
プロダクションコード 無変更(テスト追加のみ)
カバレッジ対象外 DoGreppcDlgCancel 非 null 分岐、ワーカー初期化失敗・バッチ処理の catch(...) 経路、DoGrepFile(ウィンドウGrep)は UI 実体が必要なため引き続きテスト不能
SonarCloud セキュリティ 追加コードは wcslen / wcscpy 等を不使用
文字列リテラル 出力書式の検証で使用する記号( U+30FB / U+25A0 / U+25C6)はプロダクションの書式定義と一致

@hpmy-dev hpmy-dev force-pushed the feature/grepmatulithreads branch from 299f764 to c8989f7 Compare June 19, 2026 06:53
@hpmy-dev

hpmy-dev commented Jun 19, 2026

Copy link
Copy Markdown
Author

除外ファイルに正規表現のチェックボックスを追加及び機能改善

主な変更点

  • Grep検索のマルチスレッド化: DoGrep から並列処理部分を RunParallelGrep として切り出し、スレッドプールで複数ファイルを同時に検索する
  • 除外ファイルの正規表現/ワイルドカード切替: Grepダイアログに「正規表現(R)」チェックボックスを追加し、除外ファイルパターンを正規表現とワイルドカードで切り替え可能にする
  • RAII によるスレッドプール安全停止: PoolJoinGuard を導入し、正常終了・例外伝播のどちらの経路でもスレッドプールが確実にjoinされるようにする
  • テストカバレッジの大幅追加: コマンドライン解析、ダイアログ、CGrepEnumKeysCGrepAgent フロー、異常系を含む包括的なテストを追加
image

変更の詳細

1. Grepマルチスレッド化 (CGrepAgent)

  • DoGrep 内のファイル走査・検索ループをスレッドプールベースの RunParallelGrep に切り出し
  • デッドロック対策としてロック順序の整理とバッチ処理方式を採用
  • PoolJoinGuard(RAIIガード)により、例外発生時もスレッドプールが確実にシャットダウン・joinされる
  • UIスレッドとワーカースレッド間の安全な結果集約

2. 除外ファイル 正規表現/ワイルドカード切替

項目 内容
UI Grepダイアログに IDC_CHK_EXCLUDE_FILE_REGEXP チェックボックスを追加
コマンドライン -GOPT=E フラグで正規表現モードを指定
設定永続化 CommonSetting.hm_bGrepExcludeFileRegexp を追加、プロファイルに保存/読込
パターン切替 チェック ON: DEFAULT_EXCLUDE_FILE_PATTERN_REGEX(正規表現)
チェック OFF: DEFAULT_EXCLUDE_FILE_PATTERN_WILDCARD(ワイルドカード)
CGrepEnumKeys SetFileKeysbExcludeFileRegex 引数を追加。true の場合 ! プレフィックスは正規表現除外リストへ、false の場合はワイルドカード除外リストへ振り分け

3. ダイアログレイアウト変更

  • IDD_GREP ダイアログの高さを 188 → 202 に拡張
  • 除外ファイルコンボボックスの下に「正規表現(R)」チェックボックスを配置
  • 除外フォルダー以降のコントロールを14px下方にシフト

4. テスト追加

テストファイル 内容
test-ccommandline.cpp -GOPT=E パース、全フラグONテスト
test-cdlggrep.cpp 除外パターン定数検証、ラウンドトリップ(ワイルドカード/正規表現)、デフォルトパターン
test-cgrepagent-flow.cpp CGrepAgent のフロー制御テスト
test-grep.cpp SetFileKeys の正規表現/ワイルドカード分岐、! プレフィックス処理、GetExcludeFiles 集約、DoGrep 統合テスト
test-grep-irregular.cpp 除外のみパターン、不正正規表現、大量パターン等の異常系

変更ファイル一覧 (23ファイル, +6028/-233)

ファイル一覧を展開

本体

  • sakura_core/agent/CGrepAgent.cpp — マルチスレッド化、RunParallelGrep 切り出し、PoolJoinGuard 導入
  • sakura_core/agent/CGrepAgent.hDoGrep/RunParallelGrep シグネチャ変更
  • sakura_core/grep/CGrepEnumKeys.hSetFileKeysbExcludeFileRegex 引数追加、分岐ロジック
  • sakura_core/dlg/CDlgGrep.cpp — チェックボックスUI処理、デフォルトパターン切替
  • sakura_core/dlg/CDlgGrep.hm_bExcludeFileRegularExp メンバ追加、定数定義
  • sakura_core/dlg/CDlgGrepReplace.cpp — デフォルトパターン定数を _REGEX に変更
  • sakura_core/cmd/CViewCommander_Grep.cppDoGrep 呼び出しに bGrepExcludeFileRegexp を追加
  • sakura_core/_main/CCommandLine.cpp-GOPT=E パース
  • sakura_core/_main/CControlTray.cpp — コマンドラインオプション E 出力
  • sakura_core/_main/CNormalProcess.cppDoGrep 呼び出しに引数追加
  • sakura_core/basis/GrepInfo.hbGrepExcludeFileRegexp フィールド追加
  • sakura_core/env/CommonSetting.hm_bGrepExcludeFileRegexp 設定追加
  • sakura_core/env/CShareData.cpp — 初期値設定
  • sakura_core/env/CShareData_IO.cpp — プロファイル保存/読込

リソース

  • sakura_core/sakura_rc.rc — ダイアログレイアウト変更
  • src/main/resources/sakura_rc.hIDC_CHK_EXCLUDE_FILE_REGEXP 定義

テスト

  • src/test/cpp/tests1/test-ccommandline.cpp
  • src/test/cpp/tests1/test-cdlggrep.cpp(新規)
  • src/test/cpp/tests1/test-cgrepagent-flow.cpp(新規)
  • src/test/cpp/tests1/test-grep.cpp(新規)
  • src/test/cpp/tests1/test-grep-irregular.cpp(新規)
  • sakura_core/tests1.vcxproj — テストプロジェクト追加
  • sakura_core/tests1.vcxproj.filters — テストフィルター追加

Test plan

  • ビルドが通ることを確認(x86/x64)
  • 新規追加テスト(test-grep, test-cdlggrep, test-cgrepagent-flow, test-grep-irregular, test-ccommandline)が全てパスすることを確認
  • Grepダイアログを開き「正規表現(R)」チェックボックスが表示されることを確認
  • チェックボックスON/OFFで除外ファイルのデフォルト値が正規表現/ワイルドカードに切り替わることを確認
  • チェックボックスOFFの状態で除外ファイルにワイルドカード(例: *.obj;*.exe)を指定してGrepが正常動作することを確認
  • チェックボックスONの状態で除外ファイルに正規表現(例: .*\.obj$;.*\.exe$)を指定してGrepが正常動作することを確認
  • マルチスレッドGrepが正常に動作し、結果が従来と同一であることを確認
  • Grep中にキャンセルボタンを押して正常に中断されることを確認
  • 設定がプロファイルに保存・復元されることを確認(サクラエディタ再起動後もチェック状態が維持される)
  • コマンドラインから -GOPT=E を指定してGrepが正規表現除外モードで動作することを確認

@berryzplus

Copy link
Copy Markdown
Contributor

対応ありがとうございます。

ざっくり、マージ前提で評価する条件は満たしてもらえたと思ってます。
・カバレッジ8割達成
・保守性に関するものを除いて詩的なし


仕様に関しての話をするかどうかについて若干悩み中です。

Grepマルチスレッド化の提案内容
 ・指定フォルダ配下のファイル抽出とファイル内検索をスレッド化する
 ・結果報告(GUIスレッド)はそのままなので報告順は乱れる

もしかしたら伝わるかも知れませんが、この話をするかどうかに迷いがあります。

Grepがやること
 ・検索対象ファイルの抽出(マルチスレッド化可能)
 ・ファイル内検索(マルチスレッド化可能)
 ・結果報告(マルチスレッド化不能)


保守性に関する指摘のうち、対応できるものがありそうです。

たとえばこういうの。(変数に値を入れているが読まれていない。)
image
(その変数、要らんです、と言われてます。)


保守性に関する指摘をパスして構わないと言ってたのは「かなり頑張らないと対応できない」系が多いからです。

たとえばこういうの。(ネストは3レベルまでにしましょう。)
image
(ごちゃっと書かれた関数の処理を適切な単位に分割したら実現できますが、適切な単位を見極めるのが大変です。)
(現実的に対応不可だと思うので、スルーでいいです、と言ってました。)

たとえばこういうの。(関数の認知複雑度は25までにおさえましょう。)
image
(ネストレベルと同じ話で、適切な単位を見極めるのが大変です。)
(現実的に対応不可だと思うので、スルーでいいです、と言ってました。)

@hpmy-dev

hpmy-dev commented Jun 22, 2026

Copy link
Copy Markdown
Author

対応が固まってきたんでコメントをそろそろ書き込もうとしてたところで、先にご確認ありがとうございます。

マルチスレッド

仕様に関してですが、修正に手間を掛ける割にメリットがあまりない気がします。

ファイル内検索(マルチスレッド化可能)を行うのはファイルサイズが大きい場合に
有効化と思いますが、sakuraのgrep機能はファイルサイズが大きいデータよりも
そんなに大きくもないプログラムソースをターゲットにされている方が多いと思います。
またファイル内での結果報告の順序性を保つ処理が増え、リアルタイム表示でわずかに遅延が発生し
ヒット数が多くなるほど処理が増えるかと。

>2.1.10 出力順序
>サブフォルダー間: 保証される(A→B→C の順にバッチ処理)
>サブフォルダー内のファイル間: 実行のたびに変わる場合がある(並列処理のため)
冒頭のここですが、表示順が変わってるかどうかあんまり分かりませんね。
もともとパスを一旦全て取り込んで処理をさせていたのですが、
リアルタイム表示でヒットしているにも関わらず、初回の表示までに時間が掛かったので、
現在の実装に変更してます。

保守性

保守性に関する指摘ですが、既存だったので対応しようとして止めたんですが
挙動を変えない物で対応可能なものに絞り対応し、
その為既存テストにも影響を与えてません。

変更内容(計14件)

sakura_core/agent/CGrepAgent.cpp(11件)

  • 未使用変数の削除: pszWork(dead store, DoGrep
  • dead store 解消: int nPosDiff = nColumn += nKeyLen - 1;int nPosDiff = nColumn + nKeyLen - 1;DoGrepFileWorkernColumn は以降未参照)
  • ネスト if のマージ: if(0==nLine%32){ if(bCancelled…){…} } を 1 つの条件に統合
  • 冗長な c_str() の削除: std::wstring_view を取る引数への task.fullPath / task.baseFolder / task.folder(計4箇所)
  • 冗長な型を auto に: nWorkBuildGrepHeader)/ nClampedIni / nFullPathLen
  • 透過コンパレータ: std::set<std::wstring>std::set<std::wstring, std::less<>>writtenBaseFolders / writtenFolders)、併せて #include <functional> を追加

sakura_core/dlg/CDlgGrep.cpp(1件)

  • m_bExcludeFileRegularExp をコンストラクタ本体代入から初期化リストへ移動

sakura_core/_main/CControlTray.cpp / sakura_core/cmd/CViewCommander_Grep.cpp(wcscat)(2件)
追記

  • GOPT オプション文字列を組み立てる wcscat( pOpt, … )wcscat_s( pOpt, … ) に統一(SonarCloud 指摘行 L153 / L254 は本 PR で追加した除外オプション E の行)。指摘行のみだとブロック内で表記が混在するため、当該 GOPT ブロックのアクティブ行(CControlTray 13行 / CViewCommander_Grep 14行)をまとめて _s 化。pOpt は固定長配列のためサイズは自動推論され、出力文字列は不変。コメントアウト行(CViewCommander_Grep L245)は対象外。
     
     

@hpmy-dev hpmy-dev force-pushed the feature/grepmatulithreads branch from d36fbc8 to e7c984d Compare June 22, 2026 03:54
@sonarqubecloud

Copy link
Copy Markdown

@hpmy-dev

hpmy-dev commented Jul 3, 2026

Copy link
Copy Markdown
Author

追加対応

refactor(grep): DoGrep 系シグネチャを構造体化(S107対策)+ SonarCloud 指摘対応・テスト項目の追加

概要

挙動を変えない範囲で、並列 Grep(CGrepAgent)まわりのコードの改善

  • 引数過多(S107)となっていた Grep 系メンバ関数のシグネチャを、意味のまとまりごとに構造体へ集約
  • SonarCloud で指摘されていた各種所見の機械的な修正
  • Grep 置換経路を中心としたテストの拡充
  • 付随するツール/CI/ドキュメントの軽微な追随

変更内容

1. Grep 系シグネチャの構造体化(S107 対策)

CGrepAgent.h に非所有参照の入力構造体を新設し、引数の多かった関数群を整理しました。

  • SGrepInput … 検索キー/置換/対象ファイル/フォルダー
  • SGrepSearchParams … 検索キー/SSearchOptionSGrepOption
  • SGrepEnumContext … 列挙・除外オブジェクト群(keys / exceptFiles / exceptFolders)
  • SGrepMatchInfo … ヒット位置情報(行・桁・行文字列・EOL 長・マッチ文字列など)

上記に合わせてシグネチャを更新し、全呼び出し側を修正

  • DoGrep(約20引数 → SGrepInput / SGrepOption(値渡し・内部正規化) 等へ集約)
  • DoGrepFileWorker / RunParallelGrep / DoGrepTreeEnumerate
  • SetGrepResult / FormatGrepResultLine

呼び出し側: CNormalProcess.cpp, CViewCommander_Grep.cppCommand_GREP / Command_GREP_REPLACE)。

2. SonarCloud 指摘対応(機械的・挙動を変えないもの)

  • #define の時間間隔定数を constexpr DWORD に置換
  • 翻訳単位内ローカル関数へ static 付与(EscapeStringLiteral, FormatPathList, GetHwndTitle
  • CGrepAgent() / ~CError_WriteFileOpen()= default 化、CWriteData にコピー禁止を明示
  • wcslenstd::wstring_view(p).length()wcscpy/wcsncpy/wcscat*_s 版へ移行
  • std::lock_guard/std::unique_lock<...>std::scoped_lock / CTAD、find()==end()contains()
  • デッドストア除去(nOutputHitCount)、bBom 初期化、*p = '\0'L'\0'
  • キャンセル確認処理を CheckGrepCancelUI() に抽出して重複を解消
  • nInitDone 更新を initMutex 下に置き、バリア通知の取りこぼしを防止
  • RunParallelGrep のサブフォルダー走査を早期 continue でネスト削減
  • ハードコード文言を LS(STR_MAXWINDOW) に置換

3. テスト項目の追加

  • FormatGrepResultLine / DoGrepFileWorker / RunParallelGrep の呼び出しを新シグネチャに追随
  • Grep 置換経路の網羅を追加(GA-21〜GA-34): リテラル/正規表現/単語のみ/BOM 保持(UTF-8, UTF-16LE)/SJIS 往復/出力抑制時の全行置換/否ヒット行タイプの正規化/書き込み失敗・ロック時の非破壊性/貼り付けモード
  • 除外ファイル正規表現チェックボックスのトグル(DG-28)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants