Skip to content

クリップボードからの貼り付け時のクラッシュ対応(int32_t)#2453

Open
hpmy-dev wants to merge 4 commits into
sakura-editor:masterfrom
hpmy-dev:feature/clipcrash_int32_t
Open

クリップボードからの貼り付け時のクラッシュ対応(int32_t)#2453
hpmy-dev wants to merge 4 commits into
sakura-editor:masterfrom
hpmy-dev:feature/clipcrash_int32_t

Conversation

@hpmy-dev

@hpmy-dev hpmy-dev commented Apr 27, 2026

Copy link
Copy Markdown

PR対象

  • アプリ(サクラエディタ本体)
  • テストコード

カテゴリ

  • 不具合修正
  • 改善

PR の背景

PR #2067 において SAKURAClipW 形式のヘッダ型が int から size_t に変更されたことにより、32bit版(size_t = 4バイト)と64bit版(size_t = 8バイト)の間でクリップボードのバイナリレイアウトが不一致となり、クロスビット間のコピー&ペースト時にヘッダの読み取り位置がずれて異常なサイズを確保しようとしクラッシュする問題が発生しています。

関連: #2450, #2067, Issue #2325

PRの本文を移動の為、折りたたむ。 2026.06.24 ↓

仕様・動作説明

1. SAKURAClipW ヘッダ型の int32_t 固定化

ヘッダ型を環境依存の size_t から固定長の int32_t(4バイト)に変更し、v2.4.2 リリース版(int = 4バイト)とのバイナリ互換性を回復します。ヘッダ構造体 SSakuraClipHeader を新設し、#pragma pack(push, 1) + static_assert でレイアウトを保証。

#pragma pack(push, 1)
struct SSakuraClipHeader {
    int32_t cchData;
};
#pragma pack(pop)
static_assert(sizeof(SSakuraClipHeader) == 4, "SSakuraClipHeader must be exactly 4 bytes");

フォーマット名 SAKURAClipW は変更しません(バイナリレイアウトが v2.4.2 と一致するため)。

2. SAKURAClipW メモリレイアウト

オフセット    サイズ        内容
──────────────────────────────────────
0x00          4 bytes      int32_t  cchData(文字数、符号付き)
0x04          N * 2 bytes  wchar_t  szData[cchData](文字データ)
0x04 + N*2    2 bytes      wchar_t  L'\0'(終端ヌル)

3. GlobalSize() による3段階フェイルセーフ(GetText 読み込み時)

SAKURAClipW 形式の読み込み時に、ヘッダの自己申告値を鵜呑みにせず GlobalSize() が返す実際のメモリサイズと突き合わせて検証します。

段階 チェック内容 防御対象
第1段階 pData == nullptr || cbData < sizeof(SSakuraClipHeader) ロック失敗・データ不足
第2段階 cchRaw < 0 符号反転による不正値(32/64bit混在時)
第3段階 cchData > cchMax ヘッダの自己申告値が実メモリ超過 → 破損データとして拒否

フォーマット未指定(デフォルト)の場合、SAKURAClipW の検証が失敗すると CF_UNICODETEXT へ自動フォールバックします。

4. INT32_MAX 超過時の SAKURAClipW スキップ(SetText 書き込み時)

nDataLen > INT32_MAX の場合、SAKURAClipW 形式のみをスキップし、CF_UNICODETEXT・矩形選択フラグ・行選択フラグは正常に書き込みます。

const bool bCanUseSakuraFormat = (nDataLen <= static_cast<size_t>(INT32_MAX));

これにより 2GiB 超のテキストでも CF_UNICODETEXT + 矩形選択フラグが正しく書き込まれ、ペースト側は CF_UNICODETEXT へフォールバックして動作します。

5. SIZE_T オーバーフロー防止(SetText CF_UNICODETEXT 書き込み時)

(nDataLen + 1) * sizeof(wchar_t)SIZE_T の上限を超えてオーバーフローする場合を検出し、CF_UNICODETEXT の GlobalAlloc をスキップします。

6. CLIPBOARD_MAX_CHARS による読み込み上限

CClipboard::CLIPBOARD_MAX_CHARS = INT32_MAX を定義し、CF_UNICODETEXT / CF_OEMTEXT / GetClipboardByFormat の読み込み時にデータ長を上限で切り詰めます。

7. SEH 例外ハンドリング(STATUS_NO_MEMORY 対策)

大容量データのペースト時に Windows ヒープが投げる SEH 例外 STATUS_NO_MEMORY0xC0000017)に対応するため、__try/__except を使用した安全なラッパー関数を導入しました。

CMemory::AllocBuffer 内部の malloc/realloc は C++ 例外を投げず、std::wstring::appendstd::bad_alloc の前に Windows ヒープが SEH 例外を投げるため、C++ の try-catch(std::bad_alloc&) では捕捉できません。MSVC の制約上、__try/__except はデストラクタを持つ C++ オブジェクトと共存できないため、独立した関数に分離しています。

参照: https://learn.microsoft.com/ja-jp/windows/win32/debug/using-an-exception-handler

ヘルパー関数 用途 配置ファイル
SafeAppend IWBuffer::Append の保護 CClipboard.cpp
SafeSetString CNativeW::SetString の保護 CEditView_Mouse.cpp
SafeNewBytes new BYTE[] の保護 CDropTarget.cpp
static bool SafeAppend(IWBuffer* cmemBuf, const wchar_t* pData, size_t nLen)
{
    __try {
        cmemBuf->Append(pData, nLen);
        return true;
    }
    __except( GetExceptionCode() == STATUS_NO_MEMORY
        ? EXCEPTION_EXECUTE_HANDLER
        : EXCEPTION_CONTINUE_SEARCH )
    {
        return false;
    }
}

8. CDropTarget.cpp の安全化

  • SafeNewBytes によるメモリ確保の SEH 保護
  • m_pData 配列のゼロ初期化(goto fail 時の delete[] 未定義動作防止)
  • SAKURAClipW 形式の int32_t 対応(memcpy_raw 使用)
  • nTextLen > INT32_MAX 時の SAKURAClipW スキップ(CF_UNICODETEXT・矩形選択フラグは維持)

9. CEditView_Mouse.cpp(Drop)の安全化

  • SafeSetString による SEH 保護
  • SAKURAClipW 読み込みの int32_t 対応 + GlobalSize() チェック
  • CF_UNICODETEXT / CF_TEXT の CLIPBOARD_MAX_CHARS 切り詰め
  • 破損した SAKURAClipW → CF_UNICODETEXT → CF_TEXT のフォールバックチェーン

仕様・動作説明(ファイル別ロジック詳細)

ファイル別変更詳細


1. sakura_core/_os/CClipboard.h

SSakuraClipHeader 構造体の新設

SAKURAClipW 独自クリップボード形式のバイナリヘッダを、#pragma pack(push, 1) で1バイトアラインメントに固定した構造体として定義。static_assert でサイズが正確に4バイトであることをコンパイル時に保証する。従来は size_t をキャストして直接書き込んでおり、32bit(4バイト)と64bit(8バイト)でレイアウトが不一致になっていた。

オフセット    サイズ        内容
0x00          4 bytes      int32_t  cchData(文字数)
0x04          N * 2 bytes  wchar_t  szData[cchData]
0x04 + N*2    2 bytes      wchar_t  L'\0'(終端ヌル)

CLIPBOARD_MAX_CHARS 定数の追加

CClipboard クラスの static constexpr メンバとして CLIPBOARD_MAX_CHARS = INT32_MAX を定義。ヘッダ末尾にグローバルスコープの static constexpr エイリアスも配置し、無名名前空間内の SafeAppend 等から参照可能にしている。

変更なしの項目

SetText() / GetText() の関数シグネチャ — パラメータ型 size_t nDataLen は PR #2067int から変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、int には戻さずそのまま維持する。int32_t に固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。


2. sakura_core/_os/CClipboard.cpp

SafeAppend ヘルパー関数の追加(無名名前空間)

IWBuffer::Append の呼び出しを Windows SEH(__try/__except)で保護する static 関数。STATUS_NO_MEMORY0xC0000017)を捕捉した場合のみ EXCEPTION_EXECUTE_HANDLER を返し false で復帰する。それ以外の例外は EXCEPTION_CONTINUE_SEARCH で上位に伝播させる。C++ の try-catch(std::bad_alloc&) ではなく SEH を使用する理由は、CMemory::AllocBuffer 内部の malloc/realloc が C++ 例外を投げないこと、および std::wstring::appendstd::bad_alloc を投げる前に Windows ヒープが SEH 例外を投げる場合があるため。MSVC の制約上、__try/__except はデストラクタを持つ C++ オブジェクトと同一関数に配置できないため、独立した関数に分離している。

SetText() の変更

  • bCanUseSakuraFormat フラグの導入: nDataLen <= INT32_MAX を関数冒頭で評価し、以降の SAKURAClipW 書き込み判定に使用。return false で関数全体を中断するのではなく、bSakuraText の条件に組み込むことで、CF_UNICODETEXT・矩形選択フラグ・行選択フラグは nDataLen > INT32_MAX でも正常に書き込まれる。
  • CF_UNICODETEXT の SIZE_T オーバーフロー防止: nDataLen + 1 の加算オーバーフローと cchUnicode * sizeof(wchar_t) の乗算オーバーフローを各々検出し、発生時は break で書き込みをスキップ。
  • SAKURAClipW ヘッダの int32_t 書き込み: memcpy(pClip, &header, sizeof(header))SSakuraClipHeader サイズ分を書き込む。sizeof(nDataLen)(64bit 環境で8バイト)ではなく sizeof(SSakuraClipHeader)(固定4バイト)を使用。
  • SAKURAClipW のコメント更新: データレイアウトの説明を SSakuraClipHeader ベースに修正。
  • 関数末尾の成否判定: bCanUseSakuraFormat && !hgClipSakura の場合のみ SAKURAClipW 確保失敗を false とする。nDataLen > INT32_MAX で意図的にスキップした場合は失敗扱いにしない。

GetText(IWBuffer*) の変更

  • SAKURAClipW 読み込み — 3段階フェイルセーフ:
    • 第1段階: pData == nullptr || cbData < sizeof(SSakuraClipHeader) でロック失敗・データ不足を検出。
    • 第2段階: memcpy(&cchRaw, pData, sizeof(cchRaw)) でヘッダを読み取り、cchRaw < 0 で負値(符号反転による不正値)を検出。
    • 第3段階: cchData > cchMaxcchMax = (cbData - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))でヘッダの自己申告値が実メモリを超過するケースを検出し、破損データとして拒否。
    • すべてのエラーパスで GlobalUnlock(hSakura) を呼んでからリターンまたはフォールスルー。
    • フォーマット未指定(uGetFormat == -1)の場合、検証失敗時は CF_UNICODETEXT へフォールスルー。明示指定(uGetFormat == uFormatSakuraClip)の場合は return false
    • 正常パスの AppendSafeAppend で保護。SafeAppendfalse を返した場合も、フォーマット未指定なら CF_UNICODETEXT へフォールスルー。
  • CF_UNICODETEXT 読み込み: GlobalSize() から wcsnlen で実文字数を算出し、std::min(cchTotal, CLIPBOARD_MAX_CHARS) で上限に切り詰め。SafeAppend で SEH を保護し、失敗時は GlobalUnlockreturn false
  • CF_OEMTEXT 読み込み: GlobalSize()CLIPBOARD_MAX_CHARS * sizeof(wchar_t) で切り詰めてから SJIS→UNICODE 変換。変換後の文字数も CLIPBOARD_MAX_CHARS で再切り詰め。SafeAppend で保護。

GetClipboardByFormat() の変更

  • GetLengthByMode() で取得した nLength を、モードに応じた上限値(バイナリモードは CLIPBOARD_MAX_CHARS、それ以外は CLIPBOARD_MAX_CHARS * sizeof(wchar_t))で切り詰め。
  • nEndMode == -1 で再取得した nLength にも同じ上限を適用。

3. sakura_core/_os/CDropTarget.cpp

SafeNewBytes ヘルパー関数の追加(無名名前空間)

new BYTE[]__try/__except で保護する static 関数。STATUS_NO_MEMORY 捕捉時は *ppOut = nullptr を設定して false を返す。

CDataObject::SetText() の変更

  • bUseSakuraFormat フラグの導入: nTextLen <= INT32_MAX を評価。false の場合は SAKURAClipW エントリを除外して m_nFormat を調整(2 or 3)し、CF_UNICODETEXT・CF_TEXT・矩形選択フラグは維持。
  • m_pData 配列のゼロ初期化: new DATA[m_nFormat] の直後に全エントリの datanullptr に初期化。goto fail 時に未初期化ポインタを delete[] する未定義動作を防止。
  • new BYTE[]SafeNewBytes に置き換え: CF_UNICODETEXT、CF_TEXT、SAKURAClipW、MSDEVColumnSelect の各確保を SEH で保護。失敗時は goto fail で全エントリをクリーンアップ。
  • SAKURAClipW ヘッダの int32_t 書き込み: memcpy_raw(m_pData[i].data, &cchData, sizeof(cchData))SSakuraClipHeader サイズ分を書き込む。

4. sakura_core/view/CEditView_Mouse.cpp

SafeSetString ヘルパー関数の追加(無名名前空間)

CNativeW::SetString__try/__except で保護する static 関数。STATUS_NO_MEMORY 捕捉時は false を返す。

Drop() 関数の変更 — ドロップデータ取得ループ内

  • SAKURAClipW パス: memcpy_raw(&header, pData, sizeof(header)) でヘッダを読み取り(R1: エイリアシング安全)。header.cchData >= 0 かつ cchData <= cchMaxcchMax = (nSize - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))で検証。検証失敗時は GlobalUnlockGlobalFree → CF_UNICODETEXT / CF_TEXT へフォールバック。
  • CF_UNICODETEXT パス: wcsnlen で文字数を算出し、CLIPBOARD_MAX_CHARS で切り詰め。SafeSetString で SEH を保護。失敗時は GlobalUnlockGlobalFreeE_OUTOFMEMORY を返す。
  • CF_TEXT パス: CLIPBOARD_MAX_CHARS * sizeof(wchar_t) でバイト数を切り詰めてから SJIS→UNICODE 変換。変換後の文字数も CLIPBOARD_MAX_CHARS で再切り詰め。

5. src/test/cpp/tests1/test-cclipboard.cpp

新規追加テスト

テスト名 検証内容
ClipboardMaxCharsConstant CLIPBOARD_MAX_CHARS == INT32_MAX かつ正値であることの確認
SetText7 nDataLen > INT32_MAX でサクラ形式指定時に SetClipboardData が呼ばれず false を返す
SetText8 nDataLen > INT32_MAX でもデフォルト指定で CF_UNICODETEXT と矩形選択フラグが書き込まれ true を返す。SAKURAClipW は Times(0) で呼ばれないことを検証
SetTextSizeTOverflow nDataLen = size_t::max で SIZE_T オーバーフロー防御が機能し false を返す
SakuraFormatNegativeLength ヘッダ cchData = -1GetText がフォーマット指定なしで false を返す
SakuraFormatOverflowLength ヘッダ cchData = 2 だが実データ1文字分で GetTextfalse を返す
CorruptedSakuraFallsBackToUnicode ヘッダ負値の SAKURAClipW があってもデフォルト取得で CF_UNICODETEXT にフォールバックし true を返す

既存テストの変更

  • SakuraFormatInGlobalMemory マッチャ: ヘッダの読み取りを SSakuraClipHeader 構造体経由に変更し、cchData < 0 を検出。
  • フィクスチャ CClipboardGetText: sakuraMemory の確保サイズを sizeof(SSakuraClipHeader) + ... に変更。ヘッダ書き込みを ((SSakuraClipHeader*)p)->cchData = static_cast<int32_t>(...) に変更。
  • #include <limits> を追加(std::numeric_limits<size_t>::max() 使用のため)。

PR の影響範囲

修正対象ファイル

ファイル 変更内容
sakura_core/_os/CClipboard.h SSakuraClipHeader 構造体新設、CLIPBOARD_MAX_CHARS 定数追加
sakura_core/_os/CClipboard.cpp SafeAppend 追加、SetText/GetText の int32_t 対応、GlobalSize() チェック、SEH ハンドリング
sakura_core/_os/CDropTarget.cpp SafeNewBytes 追加、int32_t 対応、ゼロ初期化、SAKURAClipW スキップ
sakura_core/view/CEditView_Mouse.cpp SafeSetString 追加、Drop() の int32_t 対応、GlobalSize() チェック
src/test/cpp/tests1/test-cclipboard.cpp SAKURAClipW ヘッダ検証テスト、フォールバックテスト等追加

変更しないもの

  • フォーマット名 SAKURAClipW(バイナリレイアウトが v2.4.2 と一致するため)
  • SetText() / GetText() の関数シグネチャ — パラメータ型 size_t nDataLen は PR INT_MAXより大きなバイト数のテキストのクリップボードへのコピーが行えるようにする変更 #2067int から変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、int には戻さずそのまま維持する。int32_t に固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。
  • CF_UNICODETEXT 関連の基本的な処理フロー
  • SetHtmlText() / HDROP 処理(大容量データに該当しない)

テスト内容

ユニットテスト(test-cclipboard.cpp)

SetText 系

テスト 内容
SetText1〜6 既存テスト(回帰確認)
SetText7 nDataLen > INT32_MAX でサクラ形式指定時に false を返す
SetText8 nDataLen > INT32_MAX でも矩形選択フラグが書き込まれる

GetText 系

テスト 内容
NoSpecifiedFormat1〜6 既存テスト(回帰確認)
SakuraFormatNegativeLength ヘッダ負値で拒否
SakuraFormatOverflowLength ヘッダ値が実メモリ超過で拒否
CorruptedSakuraFallsBackToUnicode 破損 SAKURAClipW → CF_UNICODETEXT フォールバック
SakuraFormatSuccess / Failure サクラ形式の正常取得・失敗
UnicodeTextSuccess / Failure CF_UNICODETEXT の正常取得・失敗
OemTextSuccess / Failure CF_OEMTEXT の正常取得・失敗

動作テスト

# テストシナリオ 期待結果
1 64bit版で文字列をコピー → 64bit版でペースト 正常にペーストされる
2 32bit版(v2.4.2)で文字列をコピー → 64bit版でペースト 正常にペーストされる(互換性回復)
3 64bit版で文字列をコピー → 32bit版(v2.4.2)でペースト 正常にペーストされる(互換性回復)
4 外部アプリから CF_UNICODETEXT でペースト 正常にペーストされる
5 64bit版でドラッグ&ドロップ(SAKURAClipW形式) 正常にドロップされる
6 不正なクリップボードデータ(ヘッダ負値) クラッシュせず、ペースト失敗として処理
7 不正なクリップボードデータ(ヘッダ値 > 実メモリサイズ) クラッシュせず、CF_UNICODETEXT へフォールバック
8 nDataLen > INT32_MAX の矩形選択コピー → ペースト SAKURAClipW は無し、CF_UNICODETEXT + 矩形選択フラグで動作
9 64bit版で 2GiB 超テキストをコピー → 32bit版でペースト クラッシュせず、STATUS_NO_MEMORY を SafeAppend が捕捉して false を返す

ビルド確認

構成 確認内容
Win32 Release ビルド成功、警告なし(C4018, C4267, C4244)
x64 Release ビルド成功
Win32 Debug ビルド成功
x64 Debug ビルド成功
ユニットテスト 全テスト PASS

関連 issue, PR

参考資料

@beru beru added the 🐛bug🦋 ■バグ修正(Something isn't working) label Apr 29, 2026
@beru

beru commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

差分を確認しましたがタイトル「クリップボードからの貼り付け時のクラッシュ対応(int32_t)」以外の変更も行われています。

@hpmy-dev

hpmy-dev commented May 1, 2026

Copy link
Copy Markdown
Author

はい、下記理由の為です。
・grepのマルチスレッド有りきで動作できる事を前提にしてますので含んでます。
・X64でビルドエラーを解消しないとクリップボードのX64版の動作確認が出来ませんので否応なしに取り込んでます。

・既にPRでsrc/test/cpp/tests1/test-cclipboard.cppは修正が入ってましたので、どっちみちMERGEが発生する。

上記2点はこの分です。
5236f20

@berryzplus

Copy link
Copy Markdown
Contributor

もとの実装がだいぶおかしいので、色んな切り口で検討してみる、も価値あることだと思っています。

ざっくり。

  • 生クリップボード クリップボードの生APIを直接操作する方式。クリップボードの所有者ウインドウの生ハンドルが必要あので、使い勝手はあまりよくない。ただ、指定したクリップボード形式「だけ」を扱える側面を持つのでマクロ専用として使えないことはない存在と考えられる。
  • OLEクリップボード COMインターフェース IDataObject を実装して「クリップボードデータ」を定義する Windows95 で導入された新方式。「このアプリでコピーできるデータとは?」と「特定のクリップボード形式をどうレンダリングするか」を切り離して実装できるのが特徴。

マクロ専用関数 Get / Set ClipboardByFormat の不審点

  • クリップボード形式を文字列で受け取っている
  • nMode でエンコーディング方法を受け取っている
    • -1 がバイナリモード
    • -2 がエディタと同じモード
    • それ以外なら ECodeType が指定されたとみなす
  • nEndMode で NUL終端の個数を受け取っている
    • 1, 2, 4 のいずれかを指定すると指定されたバイト数を付加した値を拡張する

テキストエディタの機能なのに、バイナリモードがある。
「エディタと同じ」のはずなのに、処理が違う。

@hpmy-dev

hpmy-dev commented May 4, 2026

Copy link
Copy Markdown
Author

確かに同様に文字コードの自動判定処理もオリジナルになってますね。最後はタイプ別設定に紐づけられるって笑

クリップボードに関してnotepadは重たい感じがありますが、notepad++などいろいろ参考にされてはどうですかね。
あとはクラッシュを優先して直されるのか、書かれている対応を含めて改良されるのか次第だと思います。

私としてはPRしている32bit版でgrepのマルチスレッドを早く32bit版ででも
リリースして頂ければと思っているぐらいです。

@hpmy-dev
hpmy-dev force-pushed the feature/clipcrash_int32_t branch from 2fc4c91 to b5dca44 Compare June 23, 2026 04:09
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

Test Results

1 109 tests   1 109 ✅  3m 57s ⏱️
  103 suites      0 💤
    1 files        0 ❌

Results for commit a6ab035.

♻️ This comment has been updated with latest results.

@hpmy-dev
hpmy-dev force-pushed the feature/clipcrash_int32_t branch 2 times, most recently from 02f5ce4 to 16924c5 Compare June 23, 2026 08:43
@hpmy-dev
hpmy-dev force-pushed the feature/clipcrash_int32_t branch from 16924c5 to 2953bf7 Compare June 24, 2026 01:11
@hpmy-dev

hpmy-dev commented Jun 24, 2026

Copy link
Copy Markdown
Author

クラッシュ対応です。
grepのマルチスレッド対応でソースを修正を繰り返し修正し乖離してきたので、クラッシュ対応ソースのみに分離。
改めて当初のPRと被る部分と追加対応した部分もあるので、書き直してます。

仕様・動作説明

1. SAKURAClipW ヘッダ型の `int32_t` 固定化

ヘッダ型を環境依存の size_t から固定長の int32_t(4バイト)に変更し、v2.4.2 リリース版(int = 4バイト)とのバイナリ互換性を回復する。ヘッダ構造体 SSakuraClipHeader を新設し、#pragma pack(push, 1) + static_assert でレイアウトを保証する。

#pragma pack(push, 1)
struct SSakuraClipHeader {
    int32_t cchData;
};
#pragma pack(pop)
static_assert(sizeof(SSakuraClipHeader) == 4, "SSakuraClipHeader must be exactly 4 bytes");

フォーマット名 SAKURAClipW は変更しない(バイナリレイアウトが v2.4.2 と一致するため)。

2. SAKURAClipW メモリレイアウト
オフセット    サイズ        内容
──────────────────────────────────────
0x00          4 bytes      int32_t  cchData(文字数、符号付き)
0x04          N * 2 bytes  wchar_t  szData[cchData](文字データ)
0x04 + N*2    2 bytes      wchar_t  L'\0'(終端ヌル)
3. CLIPBOARD_MAX_CHARS 定数と早期リターン

CClipboard::CLIPBOARD_MAX_CHARS = INT32_MAX を定義し、クリップボード操作全般の安全な上限とする。

SetText 書き込み時: 関数冒頭で nDataLen > CLIPBOARD_MAX_CHARS を判定し、超過時は return false で即座に終了する。CLIPBOARD_MAX_CHARSINT32_MAX と等価であり、この早期リターン以降は nDataLenint32_t の表現範囲に収まることが保証される。SAKURAClipW ヘッダへの static_cast<int32_t>(nDataLen) は常に安全であり、CF_UNICODETEXT・矩形選択フラグ・行選択フラグを含む全形式で無条件に書き込み処理を行う。

GetText 読み込み時: CF_UNICODETEXT / CF_OEMTEXT / GetClipboardByFormat のデータ長を CLIPBOARD_MAX_CHARS で切り詰める。

4. GlobalSize() による3段階フェイルセーフ(GetText 読み込み時)

SAKURAClipW 形式の読み込み時に、ヘッダの自己申告値を鵜呑みにせず GlobalSize() が返す実際のメモリサイズと突き合わせて検証する。検証ロジックはヘルパー関数 TryReadSakuraClipData に分離し、GetText 側のネストを浅く保つ。

段階 チェック内容 防御対象
第1段階 pData == nullptr || cbData < sizeof(SSakuraClipHeader) ロック失敗・データ不足
第2段階 cchRaw < 0 符号反転による不正値(32/64bit混在時)
第3段階 cchData > cchMax ヘッダの自己申告値が実メモリ超過 → 破損データとして拒否

フォーマット未指定(デフォルト)の場合、SAKURAClipW の検証が失敗すると CF_UNICODETEXT へ自動フォールバックする。

5. SIZE_T オーバーフロー防止(SetText CF_UNICODETEXT 書き込み時)

nDataLen + 1 の加算オーバーフローと cchUnicode * sizeof(wchar_t) の乗算オーバーフローを1つの if 文に統合し、発生時は単一の break で CF_UNICODETEXT の GlobalAlloc をスキップする。GlobalAlloc 成功時の処理を負論理 if( !hgClipText )break; から正論理 if( hgClipText ){ … } に変更する(SonarCloud S924: brain-overload 対応)。CLIPBOARD_MAX_CHARS による早期リターンが先に効くため実質到達しないが、低コストな多重防御として残置する。

6. SEH 例外ハンドリング(STATUS_NO_MEMORY 対策)

大容量データのペースト時に Windows ヒープが投げる SEH 例外 STATUS_NO_MEMORY0xC0000017)に対応するため、__try/__except を使用した安全なラッパー関数を導入する。

CMemory::AllocBuffer 内部の malloc/realloc は C++ 例外を投げず、std::wstring::appendstd::bad_alloc の前に Windows ヒープが SEH 例外を投げるため、C++ の try-catch(std::bad_alloc&) では捕捉できない。MSVC の制約上、__try/__except はデストラクタを持つ C++ オブジェクトと共存できないため、独立した関数に分離している。

ヘルパー関数 用途 配置ファイル
SafeAppend IWBuffer::Append の保護 CClipboard.cpp
TryReadSakuraClipData SAKURAClipW データの検証と読み取り CClipboard.cpp
SafeSetString CNativeW::SetString の保護 CEditView_Mouse.cpp
AdjustTripleClickCursorAbove トリプルクリック上方ドラッグ時のカーソル行頭補正 CEditView_Mouse.cpp
AdjustTripleClickCursorBelow トリプルクリック下方ドラッグ時のカーソル次行頭補正 CEditView_Mouse.cpp
SafeNewBytes new BYTE[] の保護 CDropTarget.cpp
static bool SafeAppend(IWBuffer* cmemBuf, const wchar_t* pData, size_t nLen)
{
    __try {
        cmemBuf->Append(pData, nLen);
        return true;
    }
    __except( GetExceptionCode() == STATUS_NO_MEMORY
        ? EXCEPTION_EXECUTE_HANDLER
        : EXCEPTION_CONTINUE_SEARCH )
    {
        return false;
    }
}
7. CDropTarget::CDataObject の安全化
  • SafeNewBytes による全メモリ確保の SEH 保護。確保失敗時は goto fail で全エントリをクリーンアップする。
  • m_pData 配列のゼロ初期化(goto fail 時に未初期化ポインタを delete[] する未定義動作を防止)。
  • SAKURAClipW 形式のヘッダを int32_t で書き込み(memcpy_raw 使用)。
  • nTextLen > INT32_MAX の場合は SAKURAClipW エントリを除外して m_nFormat を調整し、CF_UNICODETEXT・CF_TEXT・矩形選択フラグは維持する。
  • lpszText == nullptr 経路が fail: ラベルへフォールスルーして m_pData(null)を参照する Null pointer dereference を修正。fail: 直前に return; を追加し、fail:goto fail 経由のみの到達に限定する。
8. CEditView::Drop() の安全化
  • for(;;) ループの導入: SAKURAClipW → CF_UNICODETEXT → CF_TEXT の順にフォールバックを試行するループ構造に変更。
  • SAKURAClipW パス: memcpy_raw でエイリアシング安全にヘッダを読み取り、header.cchData >= 0 かつ cchData <= cchMax で検証。検証失敗時は GlobalUnlockGlobalFree → 次の形式へフォールバック。
  • CF_UNICODETEXT パス: wcsnlen で文字数を算出し、CLIPBOARD_MAX_CHARS で切り詰め。SafeSetString で SEH を保護。失敗時は E_OUTOFMEMORY を返す。
  • CF_TEXT パス: CLIPBOARD_MAX_CHARS * sizeof(wchar_t) でバイト数を切り詰めてから SJIS→UNICODE 変換。変換後の文字数も CLIPBOARD_MAX_CHARS で再切り詰め。
9. CEditView::Drop() の二重解放の修正

ドロップデータ取得ループ導入により、hData はループ直後の解放ブロックで GlobalUnlock/GlobalFree される。従来の関数末尾にあった同処理(// 2004.07.12 fotomo/もか メモリリークの修正)は解放済みハンドルへの二重操作となるため、末尾ブロックを削除して解放を1箇所に一本化する。


仕様・動作説明(ファイル別ロジック詳細)


1. `sakura_core/_os/CClipboard.h`

SSakuraClipHeader 構造体の新設

SAKURAClipW 独自クリップボード形式のバイナリヘッダを、#pragma pack(push, 1) で1バイトアラインメントに固定した構造体として定義する。static_assert でサイズが正確に4バイトであることをコンパイル時に保証する。従来は size_t をキャストして直接書き込んでおり、32bit(4バイト)と64bit(8バイト)でレイアウトが不一致になっていた。

オフセット    サイズ        内容
0x00          4 bytes      int32_t  cchData(文字数)
0x04          N * 2 bytes  wchar_t  szData[cchData]
0x04 + N*2    2 bytes      wchar_t  L'\0'(終端ヌル)

CLIPBOARD_MAX_CHARS 定数の追加

CClipboard クラスの static constexpr メンバとして CLIPBOARD_MAX_CHARS = INT32_MAX を定義する。ヘッダ末尾にグローバルスコープの static constexpr エイリアスも配置し、無名名前空間内の SafeAppend 等から参照可能にしている。

変更なしの項目

SetText() / GetText() の関数シグネチャ — パラメータ型 size_t nDataLen は PR #2067int から変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、int には戻さずそのまま維持する。int32_t に固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。


2. `sakura_core/_os/CClipboard.cpp`

SafeAppend ヘルパー関数の追加(無名名前空間)

IWBuffer::Append の呼び出しを Windows SEH(__try/__except)で保護する static 関数。STATUS_NO_MEMORY0xC0000017)を捕捉した場合のみ EXCEPTION_EXECUTE_HANDLER を返し false で復帰する。それ以外の例外は EXCEPTION_CONTINUE_SEARCH で上位に伝播させる。C++ の try-catch(std::bad_alloc&) ではなく SEH を使用する理由は、CMemory::AllocBuffer 内部の malloc/realloc が C++ 例外を投げないこと、および std::wstring::appendstd::bad_alloc を投げる前に Windows ヒープが SEH 例外を投げる場合があるため。MSVC の制約上、__try/__except はデストラクタを持つ C++ オブジェクトと同一関数に配置できないため、独立した関数に分離している。

TryReadSakuraClipData ヘルパー関数の追加(無名名前空間)

ロック済み SAKURAClipW データの3段階検証(ヘッダサイズ・負値・実メモリ超過)と SafeAppend による追記を行う static 関数。GetText 内のネストを3段以下に抑えるために分離している。検証失敗または SafeAppend 失敗時は false を返し、呼び出し元がフォーマット指定に応じて return false(明示指定)またはフォールスルー(既定取得)を判断する。

SetText() の変更

  • CLIPBOARD_MAX_CHARS による早期リターン: 関数冒頭で nDataLen > CLIPBOARD_MAX_CHARS を判定し、超過時は return false で全形式の書き込みを行わず即座に終了する。この時点で nDataLen <= INT32_MAX が保証されるため、以降の SAKURAClipW ヘッダへの int32_t キャストは安全である。
  • CF_UNICODETEXT の SIZE_T オーバーフロー防止: nDataLen + 1 の加算オーバーフローと cchUnicode * sizeof(wchar_t) の乗算オーバーフローを1つの if に統合し、発生時は単一の break で書き込みをスキップする。GlobalAlloc 成功時の処理を負論理 if( !hgClipText )break; から正論理 if( hgClipText ){ … } に変更する(SonarCloud S924 対応)。
  • SAKURAClipW ヘッダの int32_t 書き込み: memcpy(pClip, &header, sizeof(header))SSakuraClipHeader サイズ分(固定4バイト)を書き込む。
  • SAKURAClipW のコメント更新: データレイアウトの説明を SSakuraClipHeader ベースに修正。
  • 関数末尾の成否判定: hgClipTexthgClipSakura を個別にチェックする。

GetText(IWBuffer*) の変更

  • SAKURAClipW 読み込み: TryReadSakuraClipData を呼び出してヘッダ検証とデータコピーを行う。呼び出し元は戻り値に応じて GlobalUnlockreturn true(成功)、return false(明示指定での失敗)、またはフォールスルー(既定取得での失敗→CF_UNICODETEXT へ)を判断する。
  • CF_UNICODETEXT 読み込み: GlobalSize() から wcsnlen で実文字数を算出し、std::min(cchTotal, CLIPBOARD_MAX_CHARS) で上限に切り詰める。SafeAppend で SEH を保護し、失敗時は GlobalUnlockreturn false
  • CF_OEMTEXT 読み込み: GlobalSize()CLIPBOARD_MAX_CHARS * sizeof(wchar_t) で切り詰めてから SJIS→UNICODE 変換する。変換後の文字数も CLIPBOARD_MAX_CHARS で再切り詰める。SafeAppend で保護する。

GetClipboardByFormat() の変更

  • GetLengthByMode() で取得した nLength を、モードに応じた上限値(バイナリモードは CLIPBOARD_MAX_CHARS、それ以外は CLIPBOARD_MAX_CHARS * sizeof(wchar_t))で切り詰める。
  • nEndMode == -1 で再取得した nLength にも同じ上限を適用する。

3. `sakura_core/_os/CDropTarget.h`

#include "CClipboard.h" を追加。CDataObject::SetText 内で SSakuraClipHeaderCClipboard::GetSakuraFormat() を参照するため。


4. `sakura_core/_os/CDropTarget.cpp`

SafeNewBytes ヘルパー関数の追加(無名名前空間)

new BYTE[]__try/__except で保護する static 関数。STATUS_NO_MEMORY 捕捉時は *ppOut = nullptr を設定して false を返す。

CDataObject::SetText() の変更

  • bUseSakuraFormat フラグの導入: nTextLen <= INT32_MAX を評価する。false の場合は SAKURAClipW エントリを除外して m_nFormat を調整(2 or 3)し、CF_UNICODETEXT・CF_TEXT・矩形選択フラグは維持する。
  • m_pData 配列のゼロ初期化: new DATA[m_nFormat] の直後に全エントリの datanullptr に初期化する。goto fail 時に未初期化ポインタを delete[] する未定義動作を防止する。
  • new BYTE[]SafeNewBytes に置き換え: CF_UNICODETEXT、CF_TEXT、SAKURAClipW、MSDEVColumnSelect の各確保を SEH で保護する。失敗時は goto fail で全エントリをクリーンアップする。
  • SAKURAClipW ヘッダの int32_t 書き込み: memcpy_raw(m_pData[i].data, &cchData, sizeof(cchData))SSakuraClipHeader サイズ分を書き込む。

5. `sakura_core/view/CEditView_Mouse.cpp`

SafeSetString ヘルパー関数の追加(無名名前空間)

CNativeW::SetString__try/__except で保護する static 関数。STATUS_NO_MEMORY 捕捉時は false を返す。

Drop() 関数の変更 — ドロップデータ取得ブロック

従来はフォーマットごとの直列的な if-else で1回だけデータ取得していたが、for(;;) ループに変更して SAKURAClipW → CF_UNICODETEXT → CF_TEXT の順にフォールバックを試行する構造とする。

  • SAKURAClipW パス: memcpy_raw(&header, pData, sizeof(header)) でヘッダをエイリアシング安全に読み取る。header.cchData >= 0 かつ cchData <= cchMaxcchMax = (nSize - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))で検証する。条件判定は if( pData != nullptr && nSize >= sizeof(SSakuraClipHeader) ) に正論理で記述する。検証失敗時は GlobalUnlockGlobalFree → CF_UNICODETEXT / CF_TEXT へフォールバックする。
  • CF_UNICODETEXT パス: wcsnlen で文字数を算出し、CLIPBOARD_MAX_CHARS で切り詰める。SafeSetString で SEH を保護し、失敗時は GlobalUnlockGlobalFreeE_OUTOFMEMORY を返す。
  • CF_TEXT パス: CLIPBOARD_MAX_CHARS * sizeof(wchar_t) でバイト数を切り詰めてから SJIS→UNICODE 変換する。変換後の文字数も CLIPBOARD_MAX_CHARS で再切り詰める。

Drop() 関数の変更 — 二重解放の修正

ドロップデータ取得ループ導入により、hData はループ直後で GlobalUnlock/GlobalFree される。関数末尾にあった同処理(// 2004.07.12 fotomo/もか メモリリークの修正)は解放済みハンドルへの二重操作となるため、末尾ブロックを削除して解放を1箇所に一本化する。

AdjustTripleClickCursorAbove / AdjustTripleClickCursorBelow ヘルパー関数の追加(無名名前空間)

OnMOUSEMOVE 内のトリプルクリック+ドラッグ選択処理でネスト深度が5段に達していた部分を2つの static ヘルパー関数に抽出し、ネストを3段以下に抑える(SonarCloud S134: brain-overload 対応)。

ヘルパー関数 用途
AdjustTripleClickCursorAbove 上方ドラッグ時にカーソルを論理行頭に移動する
AdjustTripleClickCursorBelow 下方ドラッグ時にカーソルが折り返し行内に収まる場合に次の論理行頭へ補正する

6. `sakura_core/util/os.h`

GlobalSakura::size_type の int32_t 変更

テストヘルパークラス cxx::GlobalSakurasize_typesize_t から int32_t に変更する。本体の SSakuraClipHeader(4バイト)とメモリレイアウトを一致させるため。size_t(x64 で8バイト)のままではテストの CalcSizeSetTextwstringSSakuraClipHeader と異なるオフセットでデータにアクセスする。


7. `src/test/cpp/tests1/test-cclipboard.cpp`

新規追加テスト

テスト名 検証内容
ClipboardMaxCharsConstant CLIPBOARD_MAX_CHARS == INT32_MAX かつ正値であることの確認
SetText7 nDataLen > INT32_MAX でサクラ形式指定時に SetClipboardData が呼ばれず false を返す
SetText8 nDataLen > INT32_MAX でフォーマット未指定時に SetClipboardData が呼ばれず false を返す
SetTextSizeTOverflow nDataLen = SIZE_MAXCLIPBOARD_MAX_CHARS 超過により SetClipboardData が呼ばれず false を返す
SetTextEmpty nDataLen = 0 で SAKURAClipW・CF_UNICODETEXT が正常に書き込まれ true を返す
SakuraCorruptTooSmall cbData < sizeof(SSakuraClipHeader) で SAKURAClipW 検証失敗→CF_UNICODETEXT へフォールバック
SakuraCorruptNegativeHeader cchData = -1(負値ヘッダ)で明示指定時に false を返す
SakuraCorruptLengthOverrun cchData > cchMax(実メモリ超過)で CF_UNICODETEXT へフォールバック
SakuraEmptyData cchData = 0(空データ)で空文字列として正常に取得成功

既存テストへの影響

  • #include <limits> を追加(std::numeric_limits<size_t>::max() 使用のため)。
  • cxx::GlobalSakurasize_type 変更に伴い、SAKURAClipW 関連テスト(SetText1, SetText3, NoSpecifiedFormat1, SakuraFormatSuccess 等)は追加修正なしで PASS する。

PR の影響範囲

修正対象ファイル

ファイル 変更内容
sakura_core/_os/CClipboard.h SSakuraClipHeader 構造体新設、CLIPBOARD_MAX_CHARS 定数追加
sakura_core/_os/CClipboard.cpp SafeAppendTryReadSakuraClipData 追加、SetText 早期リターン・オーバーフロー防止・S924 break 削減・int32_t 対応、GetText GlobalSize() 3段階チェック・上限切り詰め
sakura_core/_os/CDropTarget.h #include "CClipboard.h" 追加
sakura_core/_os/CDropTarget.cpp SafeNewBytes 追加、int32_t 対応、ゼロ初期化、SAKURAClipW スキップ、fail: フォールスルー null deref 修正
sakura_core/view/CEditView_Mouse.cpp SafeSetStringAdjustTripleClickCursorAbove/Below 追加、Drop() フォールバックチェーン・int32_t 対応・二重解放修正、OnMOUSEMOVE S134 ネスト削減
sakura_core/util/os.h GlobalSakura::size_typeint32_t に変更
src/test/cpp/tests1/test-cclipboard.cpp CLIPBOARD_MAX_CHARS 定数テスト・SetText 早期リターンテスト・SetTextEmpty・SAKURAClipW 破損検出テスト5件追加

変更しないもの

  • フォーマット名 SAKURAClipW(バイナリレイアウトが v2.4.2 と一致するため)
  • SetText() / GetText() の関数シグネチャ — パラメータ型 size_t nDataLen は PR INT_MAXより大きなバイト数のテキストのクリップボードへのコピーが行えるようにする変更 #2067int から変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、int には戻さず維持する。int32_t に固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。
  • CF_UNICODETEXT 関連の基本的な処理フロー
  • SetHtmlText() / HDROP 処理(大容量データに該当しない)

テスト内容

ユニットテスト(test-cclipboard.cpp)

SetText 系

テスト 内容
SetText1〜6 既存テスト(回帰確認)
SetText7 nDataLen > INT32_MAX でサクラ形式指定時に false を返す
SetText8 nDataLen > INT32_MAX でフォーマット未指定時に false を返す
SetTextSizeTOverflow nDataLen = SIZE_MAXfalse を返す
SetTextEmpty nDataLen = 0 で正常書き込み成功

GetText 系

テスト 内容
NoSpecifiedFormat1〜6 既存テスト(回帰確認)
SakuraCorruptTooSmall cbData < sizeof(SSakuraClipHeader) でフォールバック
SakuraCorruptNegativeHeader cchData = -1 で明示指定時に false
SakuraCorruptLengthOverrun cchData > cchMax でフォールバック
SakuraEmptyData cchData = 0 で空文字列として成功
SakuraFormatSuccess / Failure サクラ形式の正常取得・失敗
UnicodeTextSuccess / Failure CF_UNICODETEXT の正常取得・失敗
OemTextSuccess / Failure CF_OEMTEXT の正常取得・失敗

動作テスト

# テストシナリオ 期待結果
1 64bit版で文字列をコピー → 64bit版でペースト 正常にペーストされる
2 32bit版(v2.4.2)で文字列をコピー → 64bit版でペースト 正常にペーストされる(互換性回復)
3 64bit版で文字列をコピー → 32bit版(v2.4.2)でペースト 正常にペーストされる(互換性回復)
4 外部アプリから CF_UNICODETEXT でペースト 正常にペーストされる
5 64bit版でドラッグ&ドロップ(SAKURAClipW形式) 正常にドロップされる
6 不正なクリップボードデータ(ヘッダ負値) クラッシュせず、ペースト失敗として処理される
7 不正なクリップボードデータ(ヘッダ値 > 実メモリサイズ) クラッシュせず、CF_UNICODETEXT へフォールバックする
8 nDataLen > INT32_MAX のコピー SetTextfalse を返し、クリップボードへの書き込みは行われない
9 64bit版で 2GiB 超テキストをコピー → 32bit版でペースト クラッシュせず、STATUS_NO_MEMORYSafeAppend が捕捉して false を返す

ビルド確認

構成 確認内容
Win32 Release ビルド成功、警告なし(C4018, C4267, C4244)
x64 Release ビルド成功
Win32 Debug ビルド成功
x64 Debug ビルド成功
ユニットテスト 全テスト PASS

リファクタリング(可読性・認知的複雑度の低減)

本PRには、クリップボード安全化の本体実装に加えて、変更箇所の可読性・認知的複雑度を下げるリファクタリングが含まれる(いずれも挙動を変えない範囲の整理)。

箇所 内容
CClipboard::SetText(CF_UNICODETEXT) 確保失敗の判定を負論理 if( !hgClipText )break; から正論理 if( hgClipText ){ … } に変更
CClipboard::SetText(末尾の成否判定) if( !(hgClipText && hgClipSakura) ) を2つの個別 if に分割
CClipboard::GetText(CF_UNICODETEXT) wchar_t* szData = …; if( szData ) を宣言付き if( wchar_t* szData = … ) に変更し、変数スコープを局所化
CDropTarget::CDataObject::SetText if( lpszText != nullptr ){ … }if( lpszText == nullptr ) return; のガード節に反転し、ネストを削減
CEditView::OnMOUSEMOVE トリプルクリック+ドラッグ処理を AdjustTripleClickCursorAbove / AdjustTripleClickCursorBelow に抽出し、ネストを削減
CEditView::OnLBUTTONDOWN ネストした if を結合条件にまとめ、DragDetect 判定を正論理化して早期 return に整理

注: 上記の正論理化・関数抽出・ガード節化は SonarCloud の認知的複雑度系(brain-overload 等)指摘への対応を意図しているが、具体的なルールIDと対象行は SonarCloud の Issues 一覧を一次情報として確認すること。本表は diff から確認できるリファクタリング内容のみを記載し、ルールIDの確定は含めていない。

なお、無名名前空間内の関数定義の static 指定が新規コード内で不統一(SafeAppend / TryReadSakuraClipData は無指定、SafeNewBytes / SafeSetString / AdjustTripleClickCursorBelowstatic 付き)。SonarCloud が冗長 static を指摘する場合は、static を外して統一すること。

カバレッジに関する補足

本PRの新規コードのうち、以下は性質上ユニットテストで網羅できないため新規コードカバレッジの対象外となる。いずれも防御的・環境依存・GUI/OLE 依存のコードで、テストから到達不能であることが意図された設計である。本PRの主目的であるクリップボード読み書きの中核ロジック(ヘッダ3段階検証・上限切り詰め・早期リターン)は新規テストで網羅している。

1. `sakura_core/view/CEditView_Mouse.cpp`(新規カバレッジ 0%)

本ファイルの変更(Drop() のフォールバックチェーン、SafeSetStringAdjustTripleClickCursorAbove/Below)は新規カバレッジ 0% となるが、これは以下の理由による。

  • Drop() の SAKURAClipW → CF_UNICODETEXT → CF_TEXT フォールバック経路は、生きた IDataObject・エディタビュー・ドキュメント・レイアウトマネージャを必要とする OLE ドラッグ&ドロップの GUI 動作経路であり、tests1 のユニットテストハーネスではこれらを構築できない。
  • AdjustTripleClickCursorAbove/Below も同様にビュー・レイアウト状態に依存する GUI 経路である。
  • SafeSetString__except 側は SEH 例外 STATUS_NO_MEMORY 発生時のみ到達するため誘発不能(後述 第3項)。

クリップボード読み取りの等価ロジック(ヘッダ検証・フォールバック判定)は CClipboard::GetText 側の TryReadSakuraClipData として分離してあり、そちらは SakuraCorrupt* 系の新規テストで網羅している。Drop() 側は同一ロジックを GUI 経路向けに再掲したものであり、ロジックの正当性は単体テスト側で担保される。

2. `sakura_core/_os/CDropTarget.cpp`(未到達部分のためテスト不能)

CDataObject::SetText()正常系は CopiedTextData 系テストで網羅済みだが、以下の経路は到達不能のためテストできない。

  • SafeNewBytes 失敗時の goto fail クリーンアップ経路:確保は仮想シームを経由しないグローバルな new BYTE[] のため、テストから確保失敗を注入できない。
  • SafeNewBytes / その他 SEH ラッパの __except 側:第3項のとおり誘発不能。

これらは大容量確保やメモリ枯渇という、テスト環境では再現困難な状況でのみ実行される防御コードである。

3. SEH(`__try/__except`)の例外捕捉経路

SafeAppendCClipboard.cpp)/ SafeSetStringCEditView_Mouse.cpp)/ SafeNewBytesCDropTarget.cpp)の各 __except 内の復帰経路は、Windows ヒープが STATUS_NO_MEMORY0xC0000017)を送出した場合のみ実行される。この SEH 例外は実メモリ枯渇時に OS が発生させるもので、ユニットテスト環境から決定論的に誘発する手段がない(std::bad_alloc と異なり C++ 例外機構では模擬不能)。__try 側の正常系はテスト済みで、未到達は __except 側のみ。

4. 防御的に到達不能な分岐

CClipboard::SetText の CF_UNICODETEXT 領域確保前の SIZE_T オーバーフロー検出 break は、関数冒頭の早期リターン if( nDataLen > CLIPBOARD_MAX_CHARS ) return false;== INT32_MAX)が先に効くため、到達時点で nDataLen <= INT32_MAX が保証されいかなる入力でも真にならない多重防御である。到達不能ゆえカバレッジは付かないが、低コストな安全装置として残置する(早期リターン自体は SetText7/SetText8/SetTextSizeTOverflow で検証済み)。

5. 環境依存の分岐

CClipboard::SetTextif( 0 == uFormatSakuraClip )break;RegisterClipboardFormat 失敗)等は、通常のテスト環境では必ず成功するため失敗分岐を再現できない。

テストで網羅済みの中核ロジック

未到達は上記の GUI/OLE・防御的・環境依存部分に限られ、本PRの主目的は以下のとおり単体テストで網羅している。

  • ヘッダ3段階検証: SakuraCorruptTooSmall / SakuraCorruptNegativeHeader / SakuraCorruptLengthOverrun
  • 空データの正常取得: SakuraEmptyData
  • 大容量データの早期リターン: SetText7 / SetText8 / SetTextSizeTOverflow
  • 空データの正常書き込み: SetTextEmpty
  • 上限定数の妥当性: ClipboardMaxCharsConstant

関連 issue, PR

参考資料

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
42.5% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@berryzplus

Copy link
Copy Markdown
Contributor

本件、どうするか迷ってます。
せっかく作ってもらったので活かしたいけれど、保留中の変更を上げるとconflictしまくるので。

ざっくりとした話。

  • 到達不能コードはできるだけ入れたくありません。「到達不能」と言うことは検証不能(≒レガシーコード)にあたるからです。(到達不能コードをコピペで流用するやつ、居るからね。)
  • SEHは最小限としたいです。GCCで使えないためです。現状のコードでは消えていますが、もともと CProcess::Run に入っていました。プロセスレベルのSEHブロックは復活させようと考えています。(影響大きいので、もう何年か保留中です。)個別機能「クリップボード」としては「std::bad_alloc に集約させる」ないし「考慮しない」に落とすのが妥当のように思います。
  • RegisterClipboardFormatW失敗は「再現不能な分岐を削る」が正しいように思います。

@hpmy-dev

hpmy-dev commented Jul 2, 2026

Copy link
Copy Markdown
Author

ご確認ありがとうございます。
conflictでなければ、対応可能な部分もありますが、
conflictしまくると言う事なのでcloseします。

@hpmy-dev

Copy link
Copy Markdown
Author

すみません、closeしようかと思ってたんですが、grep結果の比較にクリップボードの貼付けが正しく動作しないのでどうにかしたいです。

進めて行く方法は何かありませんかね。

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

Labels

🐛bug🦋 ■バグ修正(Something isn't working)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants