クリップボードからの貼り付け時のクラッシュ対応(int32_t)#2453
Conversation
|
差分を確認しましたがタイトル「クリップボードからの貼り付け時のクラッシュ対応(int32_t)」以外の変更も行われています。 |
|
はい、下記理由の為です。 ・既にPRでsrc/test/cpp/tests1/test-cclipboard.cppは修正が入ってましたので、どっちみちMERGEが発生する。 上記2点はこの分です。 |
|
もとの実装がだいぶおかしいので、色んな切り口で検討してみる、も価値あることだと思っています。 ざっくり。
マクロ専用関数 Get / Set ClipboardByFormat の不審点
テキストエディタの機能なのに、バイナリモードがある。 |
|
確かに同様に文字コードの自動判定処理もオリジナルになってますね。最後はタイプ別設定に紐づけられるって笑 クリップボードに関してnotepadは重たい感じがありますが、notepad++などいろいろ参考にされてはどうですかね。 私としてはPRしている32bit版でgrepのマルチスレッドを早く32bit版ででも |
2fc4c91 to
b5dca44
Compare
Test Results1 109 tests 1 109 ✅ 3m 57s ⏱️ Results for commit a6ab035. ♻️ This comment has been updated with latest results. |
02f5ce4 to
16924c5
Compare
16924c5 to
2953bf7
Compare
|
クラッシュ対応です。 仕様・動作説明1. SAKURAClipW ヘッダ型の `int32_t` 固定化ヘッダ型を環境依存の #pragma pack(push, 1)
struct SSakuraClipHeader {
int32_t cchData;
};
#pragma pack(pop)
static_assert(sizeof(SSakuraClipHeader) == 4, "SSakuraClipHeader must be exactly 4 bytes");フォーマット名 2. SAKURAClipW メモリレイアウト3. CLIPBOARD_MAX_CHARS 定数と早期リターン
SetText 書き込み時: 関数冒頭で GetText 読み込み時: CF_UNICODETEXT / CF_OEMTEXT / GetClipboardByFormat のデータ長を 4. GlobalSize() による3段階フェイルセーフ(GetText 読み込み時)SAKURAClipW 形式の読み込み時に、ヘッダの自己申告値を鵜呑みにせず
フォーマット未指定(デフォルト)の場合、SAKURAClipW の検証が失敗すると CF_UNICODETEXT へ自動フォールバックする。 5. SIZE_T オーバーフロー防止(SetText CF_UNICODETEXT 書き込み時)
6. SEH 例外ハンドリング(STATUS_NO_MEMORY 対策)大容量データのペースト時に Windows ヒープが投げる SEH 例外
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 の安全化
8. CEditView::Drop() の安全化
9. CEditView::Drop() の二重解放の修正ドロップデータ取得ループ導入により、 仕様・動作説明(ファイル別ロジック詳細)1. `sakura_core/_os/CClipboard.h`SSakuraClipHeader 構造体の新設 SAKURAClipW 独自クリップボード形式のバイナリヘッダを、 CLIPBOARD_MAX_CHARS 定数の追加
変更なしの項目
2. `sakura_core/_os/CClipboard.cpp`SafeAppend ヘルパー関数の追加(無名名前空間)
TryReadSakuraClipData ヘルパー関数の追加(無名名前空間) ロック済み SAKURAClipW データの3段階検証(ヘッダサイズ・負値・実メモリ超過)と SetText() の変更
GetText(IWBuffer*) の変更
GetClipboardByFormat() の変更
3. `sakura_core/_os/CDropTarget.h`
4. `sakura_core/_os/CDropTarget.cpp`SafeNewBytes ヘルパー関数の追加(無名名前空間)
CDataObject::SetText() の変更
5. `sakura_core/view/CEditView_Mouse.cpp`SafeSetString ヘルパー関数の追加(無名名前空間)
Drop() 関数の変更 — ドロップデータ取得ブロック 従来はフォーマットごとの直列的な
Drop() 関数の変更 — 二重解放の修正 ドロップデータ取得ループ導入により、 AdjustTripleClickCursorAbove / AdjustTripleClickCursorBelow ヘルパー関数の追加(無名名前空間)
6. `sakura_core/util/os.h`GlobalSakura::size_type の テストヘルパークラス 7. `src/test/cpp/tests1/test-cclipboard.cpp`新規追加テスト
既存テストへの影響
PR の影響範囲修正対象ファイル
変更しないもの
テスト内容ユニットテスト(test-cclipboard.cpp)SetText 系
GetText 系
動作テスト
ビルド確認
リファクタリング(可読性・認知的複雑度の低減)本PRには、クリップボード安全化の本体実装に加えて、変更箇所の可読性・認知的複雑度を下げるリファクタリングが含まれる(いずれも挙動を変えない範囲の整理)。
カバレッジに関する補足本PRの新規コードのうち、以下は性質上ユニットテストで網羅できないため新規コードカバレッジの対象外となる。いずれも防御的・環境依存・GUI/OLE 依存のコードで、テストから到達不能であることが意図された設計である。本PRの主目的であるクリップボード読み書きの中核ロジック(ヘッダ3段階検証・上限切り詰め・早期リターン)は新規テストで網羅している。 1. `sakura_core/view/CEditView_Mouse.cpp`(新規カバレッジ 0%)本ファイルの変更(
クリップボード読み取りの等価ロジック(ヘッダ検証・フォールバック判定)は 2. `sakura_core/_os/CDropTarget.cpp`(未到達部分のためテスト不能)
これらは大容量確保やメモリ枯渇という、テスト環境では再現困難な状況でのみ実行される防御コードである。 3. SEH(`__try/__except`)の例外捕捉経路
4. 防御的に到達不能な分岐
5. 環境依存の分岐
テストで網羅済みの中核ロジック未到達は上記の GUI/OLE・防御的・環境依存部分に限られ、本PRの主目的は以下のとおり単体テストで網羅している。
関連 issue, PR
参考資料
|
|
|
本件、どうするか迷ってます。 ざっくりとした話。
|
|
ご確認ありがとうございます。 |


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でレイアウトを保証。フォーマット名
SAKURAClipWは変更しません(バイナリレイアウトが v2.4.2 と一致するため)。2. SAKURAClipW メモリレイアウト
3. GlobalSize() による3段階フェイルセーフ(GetText 読み込み時)
SAKURAClipW 形式の読み込み時に、ヘッダの自己申告値を鵜呑みにせず
GlobalSize()が返す実際のメモリサイズと突き合わせて検証します。pData == nullptr || cbData < sizeof(SSakuraClipHeader)cchRaw < 0cchData > cchMaxフォーマット未指定(デフォルト)の場合、SAKURAClipW の検証が失敗すると CF_UNICODETEXT へ自動フォールバックします。
4. INT32_MAX 超過時の SAKURAClipW スキップ(SetText 書き込み時)
nDataLen > INT32_MAXの場合、SAKURAClipW 形式のみをスキップし、CF_UNICODETEXT・矩形選択フラグ・行選択フラグは正常に書き込みます。これにより 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_MEMORY(0xC0000017)に対応するため、__try/__exceptを使用した安全なラッパー関数を導入しました。CMemory::AllocBuffer内部のmalloc/reallocは C++ 例外を投げず、std::wstring::appendはstd::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
SafeAppendIWBuffer::Appendの保護CClipboard.cppSafeSetStringCNativeW::SetStringの保護CEditView_Mouse.cppSafeNewBytesnew BYTE[]の保護CDropTarget.cpp8. CDropTarget.cpp の安全化
SafeNewBytesによるメモリ確保の SEH 保護m_pData配列のゼロ初期化(goto fail時のdelete[]未定義動作防止)int32_t対応(memcpy_raw使用)nTextLen > INT32_MAX時の SAKURAClipW スキップ(CF_UNICODETEXT・矩形選択フラグは維持)9. CEditView_Mouse.cpp(Drop)の安全化
SafeSetStringによる SEH 保護int32_t対応 +GlobalSize()チェックCLIPBOARD_MAX_CHARS切り詰め仕様・動作説明(ファイル別ロジック詳細)
ファイル別変更詳細
1.
sakura_core/_os/CClipboard.hSSakuraClipHeader 構造体の新設
SAKURAClipW 独自クリップボード形式のバイナリヘッダを、
#pragma pack(push, 1)で1バイトアラインメントに固定した構造体として定義。static_assertでサイズが正確に4バイトであることをコンパイル時に保証する。従来はsize_tをキャストして直接書き込んでおり、32bit(4バイト)と64bit(8バイト)でレイアウトが不一致になっていた。CLIPBOARD_MAX_CHARS 定数の追加
CClipboardクラスのstatic constexprメンバとしてCLIPBOARD_MAX_CHARS = INT32_MAXを定義。ヘッダ末尾にグローバルスコープのstatic constexprエイリアスも配置し、無名名前空間内のSafeAppend等から参照可能にしている。変更なしの項目
SetText()/GetText()の関数シグネチャ — パラメータ型size_t nDataLenは PR #2067 でintから変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、intには戻さずそのまま維持する。int32_tに固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。2.
sakura_core/_os/CClipboard.cppSafeAppend ヘルパー関数の追加(無名名前空間)
IWBuffer::Appendの呼び出しを Windows SEH(__try/__except)で保護する static 関数。STATUS_NO_MEMORY(0xC0000017)を捕捉した場合のみEXCEPTION_EXECUTE_HANDLERを返しfalseで復帰する。それ以外の例外はEXCEPTION_CONTINUE_SEARCHで上位に伝播させる。C++ のtry-catch(std::bad_alloc&)ではなく SEH を使用する理由は、CMemory::AllocBuffer内部のmalloc/reallocが C++ 例外を投げないこと、およびstd::wstring::appendがstd::bad_allocを投げる前に Windows ヒープが SEH 例外を投げる場合があるため。MSVC の制約上、__try/__exceptはデストラクタを持つ C++ オブジェクトと同一関数に配置できないため、独立した関数に分離している。SetText() の変更
bCanUseSakuraFormatフラグの導入:nDataLen <= INT32_MAXを関数冒頭で評価し、以降の SAKURAClipW 書き込み判定に使用。return falseで関数全体を中断するのではなく、bSakuraTextの条件に組み込むことで、CF_UNICODETEXT・矩形選択フラグ・行選択フラグはnDataLen > INT32_MAXでも正常に書き込まれる。nDataLen + 1の加算オーバーフローとcchUnicode * sizeof(wchar_t)の乗算オーバーフローを各々検出し、発生時はbreakで書き込みをスキップ。int32_t書き込み:memcpy(pClip, &header, sizeof(header))でSSakuraClipHeaderサイズ分を書き込む。sizeof(nDataLen)(64bit 環境で8バイト)ではなくsizeof(SSakuraClipHeader)(固定4バイト)を使用。SSakuraClipHeaderベースに修正。bCanUseSakuraFormat && !hgClipSakuraの場合のみ SAKURAClipW 確保失敗をfalseとする。nDataLen > INT32_MAXで意図的にスキップした場合は失敗扱いにしない。GetText(IWBuffer*) の変更
pData == nullptr || cbData < sizeof(SSakuraClipHeader)でロック失敗・データ不足を検出。memcpy(&cchRaw, pData, sizeof(cchRaw))でヘッダを読み取り、cchRaw < 0で負値(符号反転による不正値)を検出。cchData > cchMax(cchMax = (cbData - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))でヘッダの自己申告値が実メモリを超過するケースを検出し、破損データとして拒否。GlobalUnlock(hSakura)を呼んでからリターンまたはフォールスルー。uGetFormat == -1)の場合、検証失敗時は CF_UNICODETEXT へフォールスルー。明示指定(uGetFormat == uFormatSakuraClip)の場合はreturn false。AppendをSafeAppendで保護。SafeAppendがfalseを返した場合も、フォーマット未指定なら CF_UNICODETEXT へフォールスルー。GlobalSize()からwcsnlenで実文字数を算出し、std::min(cchTotal, CLIPBOARD_MAX_CHARS)で上限に切り詰め。SafeAppendで SEH を保護し、失敗時はGlobalUnlock→return false。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.cppSafeNewBytes ヘルパー関数の追加(無名名前空間)
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]の直後に全エントリのdataをnullptrに初期化。goto fail時に未初期化ポインタをdelete[]する未定義動作を防止。new BYTE[]をSafeNewBytesに置き換え: CF_UNICODETEXT、CF_TEXT、SAKURAClipW、MSDEVColumnSelect の各確保を SEH で保護。失敗時はgoto failで全エントリをクリーンアップ。int32_t書き込み:memcpy_raw(m_pData[i].data, &cchData, sizeof(cchData))でSSakuraClipHeaderサイズ分を書き込む。4.
sakura_core/view/CEditView_Mouse.cppSafeSetString ヘルパー関数の追加(無名名前空間)
CNativeW::SetStringを__try/__exceptで保護する static 関数。STATUS_NO_MEMORY捕捉時はfalseを返す。Drop() 関数の変更 — ドロップデータ取得ループ内
memcpy_raw(&header, pData, sizeof(header))でヘッダを読み取り(R1: エイリアシング安全)。header.cchData >= 0かつcchData <= cchMax(cchMax = (nSize - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))で検証。検証失敗時はGlobalUnlock→GlobalFree→ CF_UNICODETEXT / CF_TEXT へフォールバック。wcsnlenで文字数を算出し、CLIPBOARD_MAX_CHARSで切り詰め。SafeSetStringで SEH を保護。失敗時はGlobalUnlock→GlobalFree→E_OUTOFMEMORYを返す。CLIPBOARD_MAX_CHARS * sizeof(wchar_t)でバイト数を切り詰めてから SJIS→UNICODE 変換。変換後の文字数もCLIPBOARD_MAX_CHARSで再切り詰め。5.
src/test/cpp/tests1/test-cclipboard.cpp新規追加テスト
ClipboardMaxCharsConstantCLIPBOARD_MAX_CHARS == INT32_MAXかつ正値であることの確認SetText7nDataLen > INT32_MAXでサクラ形式指定時にSetClipboardDataが呼ばれずfalseを返すSetText8nDataLen > INT32_MAXでもデフォルト指定で CF_UNICODETEXT と矩形選択フラグが書き込まれtrueを返す。SAKURAClipW はTimes(0)で呼ばれないことを検証SetTextSizeTOverflownDataLen = size_t::maxで SIZE_T オーバーフロー防御が機能しfalseを返すSakuraFormatNegativeLengthcchData = -1でGetTextがフォーマット指定なしでfalseを返すSakuraFormatOverflowLengthcchData = 2だが実データ1文字分でGetTextがfalseを返すCorruptedSakuraFallsBackToUnicodetrueを返す既存テストの変更
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.hSSakuraClipHeader構造体新設、CLIPBOARD_MAX_CHARS定数追加sakura_core/_os/CClipboard.cppSafeAppend追加、SetText/GetText のint32_t対応、GlobalSize()チェック、SEH ハンドリングsakura_core/_os/CDropTarget.cppSafeNewBytes追加、int32_t対応、ゼロ初期化、SAKURAClipW スキップsakura_core/view/CEditView_Mouse.cppSafeSetString追加、Drop() のint32_t対応、GlobalSize()チェックsrc/test/cpp/tests1/test-cclipboard.cpp変更しないもの
SAKURAClipW(バイナリレイアウトが v2.4.2 と一致するため)SetText()/GetText()の関数シグネチャ — パラメータ型size_t nDataLenは PR INT_MAXより大きなバイト数のテキストのクリップボードへのコピーが行えるようにする変更 #2067 でintから変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、intには戻さずそのまま維持する。int32_tに固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。SetHtmlText()/HDROP処理(大容量データに該当しない)テスト内容
ユニットテスト(test-cclipboard.cpp)
SetText 系
nDataLen > INT32_MAXでサクラ形式指定時にfalseを返すnDataLen > INT32_MAXでも矩形選択フラグが書き込まれるGetText 系
動作テスト
ビルド確認
関連 issue, PR
size_t変更の元凶となったPR参考資料
__try/__exceptのリファレンスsize_t変更の経緯とレビュアー指摘