Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

マクロMakeStringBufferW0を廃止する #1203

Conversation

berryzplus
Copy link
Contributor

@berryzplus berryzplus commented Feb 16, 2020

PR の目的

文字列バッファ参照 StringBufferW を「サイズ指定なし(=0)」で作成するためのマクロ MakeStringBufferW0 を廃止します。 変更により、StringBufferW のサイズが「不明」であるケースを考慮しなくて良くなります。

//文字列バッファ型インスタンスの生成マクロ
#define MakeStringBufferW(S) StringBufferW(S,_countof(S))
#define MakeStringBufferW0(S) StringBufferW(S,0)

StringBufferW(S,0) は書き込める文字が 0 文字(=不明)ということです。

カテゴリ

  • リファクタリング

PR の背景

サクラエディタの設定読み取り機構にはなんか問題がある気がする(根拠なし
 ↓
#1152 [WIP] CDataProfileクラスの今後のリファクタリング予定を晒してみる
 ↓
#1188 設定項目がなかったらデフォルト設定を使うようにしたい
 ↓
CDataProfileで読み取り失敗を検出できるようにする、を作ろうとしてみた
 ↓
読み取り失敗の検出と全然関係ない変更をしないといかんことに気付いた(いまここ

PRの中身自体は #1152 で見せた一連のコミットの1つをそのまま使っています。
(しょーもない書き間違いを見つけたので修正して force-push しました 😭 )

やっていること

  • CBlockComentクラスの入出力をCShareData_IO::ShareData_IO_Type_Oneから分離した。
  • CLineCommentクラスの入出力をCShareData_IO::ShareData_IO_Type_Oneから分離した。

理由

  • CBlockComentクラスのデータは、開始文字と終了文字でセットになっており、両方の設定項目を読み取れた場合にだけ設定を読み取るガードが入っている。(=データがカプセル化されている)
  • CLineComentクラスのデータは、コメントパターンと区切り位置でセットになっており、両方の設定項目を読み取れた場合にだけ設定を読み取るガードが入っている。(=データがカプセル化されている)

対策内容

  • 新設したメソッド内に適切なサイズのローカルバッファを用意し、クラス仕様に基づいた相関チェックを追加、データの設定は元々クラスに用意されていたセッターを使うように変更。

PR のメリット

  • StringBufferW のサイズが「不明(=0)」であるケースを考慮しなくて良くなります。
    読み取ったデータサイズが書き込み可能データサイズを上回っていたら「失敗」にできるようになります。

PR のデメリット (トレードオフとかあれば)

  • とくにありません。

PR の影響範囲

  • このPRはリファクタリングなので、挙動は変えていないつもりです。
  • タイプ別設定の「ブロックコメント」、「行コメント」の設定読み書きに影響する変更です。

関連チケット

#1152 [WIP] CDataProfileクラスの今後のリファクタリング予定を晒してみる
#1188 設定項目がなかったらデフォルト設定を使うようにしたい

参考資料

CShareData_IO::ShareData_IO_Type_Oneから
CBlockComent,CLineCommentの入出力を分離。
メソッド内に固定サイズのローカルバッファを用意することにより、
MakeStringBufferW0(バッファサイズ不明)を利用しなくて良いように改善。
@berryzplus berryzplus force-pushed the feature/replace_anaware_of_buffer_limits branch from 41679da to 37d6c34 Compare February 16, 2020 06:34
@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

cBlockComment.SetBlockCommentRule( szFrom, szTo );
}

return ret;
Copy link
Contributor

@beru beru Mar 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好みの問題かもしれませんが下記の書き方にした方が読み易いと思います。

	WCHAR szFrom[BLOCKCOMMENT_BUFFERSIZE];
	WCHAR szTo[BLOCKCOMMENT_BUFFERSIZE];
	bool ret;
	if( cProfile.IsReadingMode() ){
		//対になる設定が揃った場合のみ有効
		ret = cProfile.IOProfileData( pszSectionName, pszEntryKeyFrom, MakeStringBufferW( szFrom ) )
			&& cProfile.IOProfileData( pszSectionName, pszEntryKeyTo, MakeStringBufferW( szTo ) );
		// 読み込み後処理
		if( ret ){
			cBlockComment.SetBlockCommentRule( szFrom, szTo );
		}
	}
	else{
		// 書き込み準備
		::wcscpy_s( szFrom, cBlockComment.getBlockCommentFrom() );
		::wcscpy_s( szTo, cBlockComment.getBlockCommentTo() );
		//対になる設定が揃った場合のみ有効
		ret = cProfile.IOProfileData( pszSectionName, pszEntryKeyFrom, MakeStringBufferW( szFrom ) )
			&& cProfile.IOProfileData( pszSectionName, pszEntryKeyTo, MakeStringBufferW( szTo ) );
	}
	return ret;

CDataProfile::IOProfileData のメソッド呼び出しの記述が重複しちゃってますが、、まぁ…。

cLineComment.CopyTo( nDataIndex, lbuf, pos );
}

return ret;
Copy link
Contributor

@beru beru Mar 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下記の書き方にした方が読み易いと思います( https://github.com/sakura-editor/sakura/pull/1203/files#r386084038 と同様)。

	WCHAR lbuf[COMMENT_DELIMITER_BUFFERSIZE];
	bool ret;
	int pos;
	// 書き込み準備
	if( cProfile.IsReadingMode() ){
		//対になる設定が揃った場合のみ有効
		ret = cProfile.IOProfileData( pszSectionName, pszEntryKeyComment, MakeStringBufferW( lbuf ) )
			&& cProfile.IOProfileData( pszSectionName, pszEntryKeyColumn, pos );
		// 読み込み後処理
		if( ret ){
			cLineComment.CopyTo( nDataIndex, lbuf, pos );
		}
	}
	else{
		::wcscpy_s( lbuf, cLineComment.getLineComment( nDataIndex ) );
		pos = cLineComment.getLineCommentPos( nDataIndex );
		//対になる設定が揃った場合のみ有効
		ret = cProfile.IOProfileData( pszSectionName, pszEntryKeyComment, MakeStringBufferW( lbuf ) )
			&& cProfile.IOProfileData( pszSectionName, pszEntryKeyColumn, pos );
	}
	return ret;

@beru
Copy link
Contributor

beru commented Mar 1, 2020

(このPRではなくそれ以前の)元実装に色々と気になる点があります。

元実装では書き込みモード時に DataProfile::IOProfileData メソッドを呼ぶ際に MakeStringBufferW0 マクロを使って StringBufferW 型のインスタンスを仲介する実装になっています。

どうしてそういう実装になっているのを推測するに DataProfile::IOProfileData メソッドから呼び出す文字列変換処理 DataProfile::value_to_profile メソッドの変換元の引数の型が const char*const wchar_t* の場合のオーバーロードが無いからではないかと思います。

下記のメソッドが呼び出しされますが、StringBufferW::nDataCount は参照されないので元実装の
MakeStringBufferW0 マクロを使うやり方でも問題が起きないのかなと思います。

	void value_to_profile(const StringBufferW& value, wstring* profile)
	{
		*profile = value.pData;
	}

さてconst char*const wchar_t* のオーバーロードが無いのなら追加すれば良いような気もしますが、もしかしたら何かの理由で意図的に追加していないのかもしれません。変換元の型が StringBufferWStaticString の場合のオーバーロードは存在するので、変換元の型が文字列なのはイリーガルという設計意図は無いような気もしますが…よくわかりません。あと昔のC++には std::basic_string_viewstd::array は存在しなかったかので色々な自前の型が定義して記述されてますね。文字列処理の記述に統一性が無いところは頑張ってなんとかしないといけないとこな気がしますが、やっぱ大変そうなので放置民です。

@berryzplus
Copy link
Contributor Author

berryzplus commented Mar 1, 2020

さてconst char* や const wchar_t* のオーバーロードが無いのなら追加すれば良いような気もしますが、もしかしたら何かの理由で意図的に追加していないのかもしれません。変換元の型が StringBufferW や StaticString の場合のオーバーロードは存在するので

const char*const wchar_t* は単独ではサイズが不明だからかと思います。
StringBufferWStaticString には書き込んでよいサイズ情報が含まれています。

まぁ、サイズが分かる型のみをサポートしてるはずなのにサイズチェックはしてないんで、溢れたら落ちる間抜け実装なんですよね 😄

このPRの背景には、溢れたら落ちるを解消したい、ってことも含まれています。
そのためには、StringBufferW をサイズ不明で構築させるマクロの存在が邪魔なんです 😃

IOProfileData系のメソッドが設定値に非const型を要求するのは、読み取りと書き込みの処理を別々に書いてどちらかを書き漏らすトラブルを防ぐためかと思っています。普通はやんないと思うのでそれって杞憂ですよね。

既存コードの突っ込みどころには、一括置換で対処できるもの以外できれば触れたくないです。
だからまぁ、IOProfileDataが1回だけ記述される構成にしてあるのは意図的なものです。

このPRが通ると、StringBufferWを「サイズが判明している書き込み可能なバッファを参照するクラス」に定義しなおせるようになります。

現状で5,6個ある文字列系クラスは、たぶん StringBuffer<T> の派生クラスとして定義しなおせる気がしています。最終的にそれをやるかどうかはPRの成否には関係ないと思ってますが。

@beru
Copy link
Contributor

beru commented Mar 1, 2020

さてconst char* や const wchar_t* のオーバーロードが無いのなら追加すれば良いような気もしますが、もしかしたら何かの理由で意図的に追加していないのかもしれません。変換元の型が StringBufferW や StaticString の場合のオーバーロードは存在するので

const char*const wchar_t* は単独ではサイズが不明だからかと思います。
‘StringBufferWStaticString` には書き込んでよいサイズ情報が含まれています。

元のコードを実装した人がNUL終端文字列をサポートしなかった理由はもはや想像の世界ですね…。

まぁ、サイズが分かる型のみをサポートしてるはずなのにサイズチェックはしてないんで、溢れたら落ちる間抜け実装なんですよね 😄

このPRの背景には、溢れたら落ちるを解消したい、ってことも含まれています。
そのためには、StringBufferW をサイズ不明で構築させるマクロの存在が邪魔なんです 😃

元の実装でそういうケースがあるという事でしょうか? MakeStringBufferW0 マクロは CShareData_IO::ShareData_IO_Type_One メソッドで書き込みモードでしか使われていないのでその場合は問題無さそうに見えました。実際にそういうケースが有るかは不明だけど潜在的な不具合要因を回避するために存在自体を消したいという事でしょうか?

IOProfileData系のメソッドが設定値に非const型を要求するのは、読み取りと書き込みの処理を別々に書いてどちらかを書き漏らすトラブルを防ぐためかと思っています。普通はやんないと思うのでそれって杞憂ですよね。

読み書きのシリアライズ処理は工夫すれば読み書きの記述を結構共通化出来るのでサクラエディタの記述を見るともっとスマートに書けるだろう的に思ってしまいますが、現実的にはコード量が多いしケースバイケースなとこも多いから結局茨の道かもと思って変更諦めました。

既存コードの突っ込みどころには、一括置換で対処できるもの以外できれば触れたくないです。
だからまぁ、IOProfileDataが1回だけ記述される構成にしてあるのは意図的なものです。

うーん、でも分岐が多いと流れが追いにくいです…。まぁそんな長くないので無理難題ではないですが…。IOProfileDataの重複記述はクロージャを使えば解消出来ると思いますが、そうする事で可読性が上がるかは微妙なとこですね。

このPRが通ると、StringBufferWを「サイズが判明している書き込み可能なバッファを参照するクラス」に定義しなおせるようになります。

そもそもバッファ参照用の型なのに名前が StringBufferW なのが違和感が有るんですよね。その名前だとその型自体がバッファに思えてきます。

現状で5,6個ある文字列系クラスは、たぶん StringBuffer<T> の派生クラスとして定義しなおせる気がしています。最終的にそれをやるかどうかはPRの成否には関係ないと思ってますが。

自前型を用意しないでも std::wstringstd::wstring_viewstd::array<wchar_t, N> の3つだけで置き換えは不可能ではないと思うんです。だけど既存コードの記述が様々なので書き換えするプログラミングのコストが膨大になると思います。

Copy link
Contributor

@beru beru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRのメリット(というか現状の実装だとどういうケースで問題が有るのか)がちょっと分かっていませんが、動作としては害は無いと思います。

@beru
Copy link
Contributor

beru commented Mar 7, 2020

const char*const wchar_t* は単独ではサイズが不明だからかと思います。
StringBufferWStaticString には書き込んでよいサイズ情報が含まれています。

まぁ、サイズが分かる型のみをサポートしてるはずなのにサイズチェックはしてないんで、溢れたら落ちる間抜け実装なんですよね 😄

このPRの背景には、溢れたら落ちるを解消したい、ってことも含まれています。
そのためには、StringBufferW をサイズ不明で構築させるマクロの存在が邪魔なんです 😃

こちらですが実使用で溢れて落ちる挙動に入るケースが判明しているなら、再現確認をしたいので記載してもらえると助かります。というか仮に実使用で落ちるパターンがあってそれが修正されるならカテゴリがリファクタリングだけではないですね。

@berryzplus
Copy link
Contributor Author

こちらですが実使用で溢れて落ちる挙動に入るケースが判明しているなら、再現確認をしたいので記載してもらえると助かります。というか仮に実使用で落ちるパターンがあってそれが修正されるならカテゴリがリファクタリングだけではないですね。

ここの使用例だと落ちません。

このコメント #1203 で指摘されてるとおり、 MakeStringBufferW0 の利用箇所は 読み取り側だけ だから、というのが理由です。

この点をクローズアップして対策すると CDataProfile::IOProfileData の利用をやめて、 CProfile::SetProfileDataImp を public にする感じの方向になります。

だからといって、このPRで提案している「 MakeStringBufferW0 の排除」にメリットがないかというと違う気がします。「MakeStringBufferW0 には害がある」というのはぼくの直観ですが、 実害 を立証できないので迷っている感じです。

@berryzplus
Copy link
Contributor Author

あれ、というか approve もらってますね(キヅイテナカッタ

@beru
Copy link
Contributor

beru commented Mar 7, 2020

ここの使用例だと落ちません。

このコメント #1203 で指摘されてるとおり、 MakeStringBufferW0 の利用箇所は 読み取り側だけ だから、というのが理由です。

自分が確認した限りでは MakeStringBufferW0 の利用箇所は CShareData_IO::ShareData_IO_Type_One において書き込みモード時のみでした。なので 読み取り側だけ だから というのは??と思いました。まぁでもいちいち詳細確認して間違い起きないようにコメントするのはかったるいですね。

この点をクローズアップして対策すると CDataProfile::IOProfileData の利用をやめて、 CProfile::SetProfileDataImp を public にする感じの方向になります。

自分の考えはそれとは違います。まず CDataProfile::IOProfileData から呼び出している value_to_profileprofile_to_value をメソッドではなく関数にした方が良いと思います。これらの実装ではメンバー変数は参照していないですしおすし。あと公開関数にする事で別の型への対応を多重定義の関数を更に追加する事で行えます。

だからといって、このPRで提案している「 MakeStringBufferW0 の排除」にメリットがないかというと違う気がします。「MakeStringBufferW0 には害がある」というのはぼくの直観ですが、 実害 を立証できないので迷っている感じです。

下記のマクロ実装は時代遅れなので排除する事自体は良いと思います。

//文字列バッファ型インスタンスの生成マクロ
#define MakeStringBufferW(S) StringBufferW(S,_countof(S))
#define MakeStringBufferW0(S) StringBufferW(S,0)

オーバーロードするコンストラクタでテンプレート引数から固定長配列の要素数は取得出来るので、MakeStringBufferW マクロも使わないで良いんじゃないかと思います。

ただあえて動作しているものを無暗に触るなと〆られる可能性も…。

@berryzplus
Copy link
Contributor Author

自分が確認した限りでは MakeStringBufferW0 の利用箇所は CShareData_IO::ShareData_IO_Type_One において書き込みモード時のみでした。なので 読み取り側だけ だから というのは??と思いました。

設定ファイルに書き込むモードは、設定値から値を読み取るモードです。
逆に、設定ファイルから読み込むモードは、設定値に値を書き込むモードです。

MakeStringBufferW0 の問題は、設定値に値を書き込みたいときに書き込んでよいサイズが不明である点にあります。実際の利用が、設定値から値を読み取る場合のみに使われているので問題ないって話でした。

分かりづらいコメントしててすんません。

@berryzplus
Copy link
Contributor Author

berryzplus commented Mar 8, 2020

まず CDataProfile::IOProfileData から呼び出している value_to_profile と profile_to_value をメソッドではなく関数にした方が良いと思います。これらの実装ではメンバー変数は参照していないですしおすし。あと公開関数にする事で別の型への対応を多重定義の関数を更に追加する事で行えます。

その変更自体は次のPRで予定しています。設定値と std::wstring との相互変換を public で static な公開関数として他のクラスからも呼べるようにしたいと思っています。

オーバーロードするコンストラクタでテンプレート引数から固定長配列の要素数は取得出来るので、MakeStringBufferW マクロも使わないで良いんじゃないかと思います。

これは正直やりたいんですが、いっぱい変更しているように見えるので次のPRには含めないかもしれません。

@berryzplus
Copy link
Contributor Author

ん?完全なフリー関数と public static なメンバ関数って違う・・・?w

@berryzplus
Copy link
Contributor Author

レビューありがとうございました。
次ステップに進みたいと思います。
何か問題が見つかったらいつも通り別PRで対処していきたいと思います。

@berryzplus berryzplus merged commit 3f6514d into sakura-editor:master Mar 8, 2020
@berryzplus berryzplus deleted the feature/replace_anaware_of_buffer_limits branch March 8, 2020 15:46
@beru
Copy link
Contributor

beru commented Mar 8, 2020

ん?完全なフリー関数と public static なメンバ関数って違う・・・?w

C言語で static 関数というとファイルスコープに限定した関数ですね。
public static なメンバ関数とはスコープが違うし標準C++には partial class も無いので拡張しにくいです。
C++のTMPでMixin頑張るのは闇要素高いのでフリー関数のオーバーロードでリンク時に解決が単純で良い気がします。

@berryzplus
Copy link
Contributor Author

berryzplus commented Mar 8, 2020

ではフリー関数として公開する方法で精査していきます。

名前重複があるとめんどいんで namespace を使う感じになると思います。

検討している候補は basissakuraskr...

設定ファイル読込イメージ

std::wstring profile = L"255";
int value = 0;
bool ret = basis::TryParse( profile, value ); //元profile_to_value関数

設定ファイル書込イメージ

int value = 255;
std::wstring profile;
profile = basis::ConvertToString( value ); //元value_to_profile関数

PRまでには2~3日かかると思います。

@m-tmatma m-tmatma added this to the v2.4.0 milestone Apr 19, 2020
HoppingTappy pushed a commit to HoppingTappy/sakura that referenced this pull request Jun 16, 2020
…_anaware_of_buffer_limits

マクロMakeStringBufferW0を廃止する
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.

4 participants