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

フォルダ選択ダイアログを SHBrowseForFolder() でなく IFileDialog を使用するように変更 #1609

Merged
merged 9 commits into from Apr 10, 2021

Conversation

Ocelot1210
Copy link
Contributor

PR の目的

フォルダ選択ダイアログの操作性を向上させる事が目的です

カテゴリ

  • 仕様変更

PR の背景

  • SHBrowseForFolder はツリー形式の UI のため操作性に難があります(特に深い階層のフォルダを選択する場合)
  • Windows Vista 以降では SHBrowseForFolder ではなく、 FOS_PICKFOLDERS オプションを指定した IFileDialog を使用する事が推奨されています(詳細は参考資料 No.1 をご参照下さい)

PR のメリット

フォルダ選択ダイアログの操作性が向上します

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

Vista 以前の OS (2000/XP) でフォルダ選択ダイアログが動作しなくなります

仕様・動作説明

変更前

SHBrowseForFolder を使用してフォルダ選択ダイアログを表示します

変更後

IFileDialogFOS_PICKFOLDERS オプションを指定し、フォルダ選択ダイアログを表示します

PR の影響範囲

フォルダ選択ダイアログを表示する以下の項目に影響があります

  1. メニュー→設定→共通設定→バックアップ→フォルダ名
  2. メニュー→設定→共通設定→プラグイン→設定→値
    • 確認用に使用したプラグインは参考資料 No.2 をご参照下さい
  3. メニュー→設定→共通設定→マクロ→マクロ一覧
  4. メニュー→設定→共通設定→支援→migemo設定→辞書
  5. メニュー→設定→共通設定→編集→ファイルダイアログの初期位置→指定フォルダ
  6. メニュー→ツール→外部コマンド実行→カレントディレクトリ
  7. メニュー→検索→ファイルツリー→設定→パス
  8. メニュー→検索→Grep→検索場所
  9. メニュー→検索→タグファイルの作成→タグ作成フォルダ
  10. マクロ専用関数 FolderDialog() が呼ばれた際

テスト内容

テスト1

手順

  1. PR の影響範囲の 1~9 にあるフォルダ選択ダイアログが表示されるボタンを押す
  2. フォルダ選択ダイアログのタイトルが変更前後で差異が無い事を確認する
  3. フォルダ選択ダイアログにて「キャンセル」ボタンを押した際の動作が変更前後で差異が無い事を確認する
  4. フォルダ選択ダイアログにて「フォルダーの選択」ボタンを押した際の動作が変更前後で差異が無い事を確認する

テスト2

手順

  1. 以下の内容のファイル(拡張子は.js)を作成し、メニュー→ツール→名前を指定してマクロ実行で開く
result = Editor.FolderDialog("フォルダを選択してください", "C:\\Windows")
Editor.MessageBox(result, 0x40)
  1. フォルダ選択ダイアログのタイトルが変更前後で差異が無い事を確認する
  2. フォルダ選択ダイアログにて「キャンセル」ボタンを押した際の動作が変更前後で差異が無い事を確認する
  3. フォルダ選択ダイアログにて「フォルダーの選択」ボタンを押した際の動作が変更前後で差異が無い事を確認する

関連 issue, PR

直接的な issue, PR はありませんが、 @beru さんが #1431 にて言及されております

参考資料

  1. SHBrowseForFolderW function (shlobj_core.h)
  2. テスト用プラグイン.zip

@AppVeyorBot
Copy link

@berryzplus
Copy link
Contributor

@Ocelot1210 さん はじめましてでしょうか。PR趣旨は了解です。

Security Hotspot E 1 Security Hotspot

いまのところ、レビューして「問題ありません」で解決した実績はないです。
wcscpy_s を使うように修正するだけで良いので対応をお願いしたいです。

Code Smell A 4 Code Smells

  • C++ではポインタに NULL(=数値0のこと) を使うべきでないそうです。
  • ネストしたコードブロックには、通常「名前」を付けられます。
    関数化して処理の「名前」を明示したほうがメンテナンスしやすくなるそうです。
  • 関数の戻り値が使用されないパスがあるようです。
    戻り値は処理の成否を表すことが多いので、取得したら何らかの判定を行うべきと思います。

SonarCloud指摘に対する対処は「絶対必要」というわけではないです。

  • 対処することによって修正を誤る危険がある。
  • 対処するのがすっごくめんどい。
  • 明らかに Fault Positive (=擬陽性、解析ツールのバグじゃね?)。

「対応しない理由」が(普通に考えて)納得できるようなら「スルー」で良いと思っています。

@Ocelot1210
Copy link
Contributor Author

@berryzplus さん、はじめまして
PR の内容のご確認及びアドバイスを頂きありがとうございます
普段 C++ を触らないので勉強になりました
GitHub / SonarCloud をあまり理解していないためお手数をおかけして申し訳ございません
ご指摘頂いた内容を対応いたしました

wcscpy_s を使うように修正するだけで良いので対応をお願いしたいです。

本件、格納先の要素数を渡すため SelectDir 関数の引数が増えましたがご容赦ください

@AppVeyorBot
Copy link

Build sakura 1.0.3612 failed (commit 6d2c2a9783 by @Ocelot1210)

@berryzplus
Copy link
Contributor

appveyorのビルド失敗が意味不明なのでリビルドかけときました。

@AppVeyorBot
Copy link

berryzplus
berryzplus previously approved these changes Mar 31, 2021
Copy link
Contributor

@berryzplus berryzplus left a comment

Choose a reason for hiding this comment

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

判断できずに放置してしまっていました。
対応したい変更はできてそうなので結論を出しておきます。

「分かんなかったらapprove」で良いこともあると思うんですよね。
人間、一人で全領域のスペシャリストにはなれないのだから。
それで問題を起こさないようにするためのCIで、SonarCloudだと思っています。

一応、今回は動かしたっす 😃

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.

テンプレート引数で固定長配列の要素数を取る SelectDir のオーバーロード関数を追加する事で、呼び出し側で最後の引数指定(例えば _countof(szPath))を省略出来るので便利かもと思いました。

@beru beru added the specification change ■仕様変更 label Mar 31, 2021
@Ocelot1210
Copy link
Contributor Author

@beru さん、PR のご確認ありがとうございます
ご指摘頂いた点を対応いたしました

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

berryzplus
berryzplus previously approved these changes Apr 4, 2021
Copy link
Contributor

@berryzplus berryzplus left a comment

Choose a reason for hiding this comment

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

細かいところは後まわしでもいいような気がするんですよね・・・。

BOOL SelectDir(HWND hWnd, const WCHAR* pszTitle, const WCHAR* pszInitFolder, WCHAR* strFolderName, size_t nMaxCount ); /* フォルダ選択ダイアログ */

template <size_t nMaxCount>
BOOL SelectDir(HWND hWnd, const WCHAR* pszTitle, const WCHAR* pszInitFolder, WCHAR(&strFolderName)[nMaxCount])
Copy link
Contributor

Choose a reason for hiding this comment

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

ここ、SonarCloudの警告がでました。
対応は必須でないと思いますが、あるべき姿で考えると「戻り値BOOL」がマズいのかもです。

std::wstring SelectDir(
  HWND hWnd,
  std::wstring_view title,
  std::wstring_view initFolder);

@@ -259,7 +259,7 @@ BOOL CDlgExec::OnBnClicked( int wID )

case IDC_BUTTON_REFERENCE2:
{
if( SelectDir( GetHwnd(), LS(STR_DLGEXEC_SELECT_CURDIR), &m_szCurDir[0], &m_szCurDir[0] ) ){
if( SelectDir( GetHwnd(), LS(STR_DLGEXEC_SELECT_CURDIR), &m_szCurDir[0], &m_szCurDir[0], m_szCurDir.GetBufferCount() ) ){
Copy link
Contributor

Choose a reason for hiding this comment

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

戻り値BOOLをやめた場合、ここはこんな感じになります。

if( auto selectedDir = SelectDir( GetHwnd(), LS(STR_DLGEXEC_SELECT_CURDIR), &m_szCurDir[0] ); selectedDir.length() > 0 ){
  m_szCurDir = selectedDir.c_str();

m_szCurDir の宣言型がおかしいのが原因で、ロジックが書きづらくなっています。
対応はしなくてもいいんじゃないかなぁ、と思います。

@param [in] pDialog 設定対象のダイアログ
@param [in] pszInitFolder 初期フォルダに設定したいパス
*/
static void SetInitialDir( Microsoft::WRL::ComPtr<IFileDialog> pDialog, const WCHAR* pszInitFolder )
Copy link
Contributor

Choose a reason for hiding this comment

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

別に正しく書く必要はありませんが、使い方が間違っています。

Suggested change
static void SetInitialDir( Microsoft::WRL::ComPtr<IFileDialog> pDialog, const WCHAR* pszInitFolder )
static void SetInitialDir( IFileDialog* pDialog, const WCHAR* pszInitFolder )

あとは好みですが、第二引数は std::wstring_view がいいっす。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

対応いたしました
ご指摘頂きありがとうございます

wcscpy_s( szInitFolder, _countof(szInitFolder), pszInitFolder );

// フォルダの最後が半角かつ'\\'の場合は、取り除く "c:\\"等のルートは取り除かない
CutLastYenFromDirectoryPath( szInitFolder );
Copy link
Contributor

Choose a reason for hiding this comment

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

😃

@berryzplus berryzplus dismissed their stale review April 4, 2021 07:45

stdafx.hのマージでコンフリクトが発生したため。

@berryzplus
Copy link
Contributor

berryzplus commented Apr 4, 2021

あるべき姿で考えるとSelectDirのシグニチャはこうなんですよね。

std::filesystem::path SelectDir(
  HWND hWnd,
  std::wstring_view title,
  std::filesystem::path initFolder);

このグローバル関数が扱う対象は何でしょう?

  • パス文字列 = std::filesystem::path
  • 文字列参照 = std::wstring_view
  • 出力文字列 = std::wstring&
  • 共有メモリ内の文字列 = SFilePath = StaticString<wchar_t, 260>

@AppVeyorBot
Copy link

sakura_core/util/shell.cpp Outdated Show resolved Hide resolved
@AppVeyorBot
Copy link

sakura_core/util/shell.cpp Outdated Show resolved Hide resolved
@AppVeyorBot
Copy link

return FALSE;
}

if ( nMaxCount < wcslen(pszResult) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

この行でSecurity Hotspotが検出されとります。

Security Hotspot E 1 Security Hotspot

Suggested change
if ( nMaxCount < wcslen(pszResult) ) {
if ( pszResult != nullptr && nMaxCount < wcsnlen( pszResult, nMaxCount ) ) {

になるのかなぁ・・・。

Copy link
Contributor

Choose a reason for hiding this comment

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

IShellItem::GetDisplayName が成功時に pszResult の値が nullptr になる事があるのかどうかは疑問ですが、仮にそうなる事があるとしてその場合に wcsnlen を呼ばないようにしたいという事でしょうか?

ただこの記述だと仮に pszResult が nullptr の時に wcsnlen は呼ばれないですが、wcscpy_s は呼ばれますよね。

それならばそもそも pszResult が nullptr の場合は return FALSE; した方が良いと思いますね。まぁ無いと思いますが…。

Copy link
Contributor

@beru beru Apr 9, 2021

Choose a reason for hiding this comment

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

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strnlen-strnlen-s?view=msvc-160

の説明に下記のように書かれていました。

strnlen, wcsnlen, and _mbsnlen do not validate their parameters. If str is NULL, an access violation occurs.

strnlen_s and wcsnlen_s validate their parameters. If str is NULL, the functions return 0.

実際に pszResult が NULL 値になるとは思いませんが使うなら wcsnlen より wcsnlen_s が良いと思います。

あと wcsnlen はC標準規格には無いのか下記のページには載っていませんでした。

https://en.cppreference.com/w/c/string/wide/wcslen

元の wcslen 関数を呼び出す記述だと SonarCloud が Security Hotspot を検出してきますが、実際には問題は無いと思うんですよね。GetDisplayName 成功時に取得した文字列は有効領域と見なせると思います。

なお wcslen の代わりに wcsnlen_s を使う事による利点としては、SonarCloud が Security Hotspot を検出しなくなるのと、あと文字列長が出力先バッファ長を超える場合にそこでループを中断させる事が出来るので無駄が少ないですね。

というか IShellItemDisplayName のバッファ長を返すメソッドを用意してくれていたら良かったんですが探しても見つかりませんでした。

sakura_core/util/shell.cpp Show resolved Hide resolved
sakura_core/util/shell.cpp Outdated Show resolved Hide resolved
@berryzplus
Copy link
Contributor

util/shell.cpp 104行目からの修正案です。

	BOOL bRet = TRUE;
	if ( STRUNCATE == wcsncpy_s( strFolderName, nMaxCount, pszResult, _TRUNCATE ) ) {
		wcscpy_s( strFolderName, nMaxCount, L"" );
		bRet = FALSE;
	}

	CoTaskMemFree( pszResult );

	return bRet;

これなら pszResult の長さを取得する必要も、初期値を設定する必要もないです。

@beru
Copy link
Contributor

beru commented Apr 9, 2021

util/shell.cpp 104行目からの修正案です。

	BOOL bRet = TRUE;
	if ( STRUNCATE == wcsncpy_s( strFolderName, nMaxCount, pszResult, _TRUNCATE ) ) {
		wcscpy_s( strFolderName, nMaxCount, L"" );
		bRet = FALSE;
	}

	CoTaskMemFree( pszResult );

	return bRet;

これなら pszResult の長さを取得する必要も、初期値を設定する必要もないです。

wcsncpy_s を使う方法は #1609 (comment) でもコメントしましたが戻り値を確認して FALSE を返す事を思いつきませんでした。その方法は良いですね。

なお戻り値の判定は STRUNCATE ではなくて 0 以外かどうかの方が良い気がします。

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strncpy-s-strncpy-s-l-wcsncpy-s-wcsncpy-s-l-mbsncpy-s-mbsncpy-s-l?view=msvc-160#return-value

Zero if successful, STRUNCATE if truncation occurred, otherwise an error code.

どういう時にエラーコードが返るのか?については
https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strncpy-s-strncpy-s-l-wcsncpy-s-wcsncpy-s-l-mbsncpy-s-mbsncpy-s-l?view=msvc-160#error-conditions

に書かれているので転載します。

errno_t wcsncpy_s(
   wchar_t *strDest,
   size_t numberOfElements,
   const wchar_t *strSource,
   size_t count
);
strDest numberOfElements strSource Return value Contents of strDest
NULL any any EINVAL not modified
any any NULL EINVAL strDest[0] set to 0
any 0 any EINVAL not modified
not NULL too small any ERANGE strDest[0] set to 0

例えば SelectDirnMaxCount 引数にゼロを渡したら(ひどい利用方法)、その値が wcsncpy_s 関数の第2引数の numberOfElements に渡されるのでこの場合は EINVAL が返るんですかね?動作確認はしていませんが…。

なお空文字を設定するのに

wcscpy_s( strFolderName, nMaxCount, L"" );

を呼び出すのではなく strFolderName[0] = 0; でも良いのではないかと思ったんですが、strFolderName が nullptr の場合がありそうですね。なお wcscpy_s だと NULL を渡すとエラーハンドラが呼ばれるので wcsncpy_s にした方が良いと思います。

というか nullptr 引数のチェックは SelectDir 関数の冒頭で行うようにした方が良いのかもしれないですね。IFileDialog が働き損になるので。

beru
beru previously approved these changes Apr 9, 2021
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の説明の影響範囲に列挙されていた箇所で、フォルダ選択ダイアログにVista以降のCommon Item Dialogが使われるようになった事の動作確認をしました。


SonarCloud が wcslen の呼び出しを Security Hotspot として検出していますが、実際に今のコードでどういうケースでどういう問題が起きるのか(起こせるのか)は分かりません。

自分が分かっている範囲では SelectDir 関数の引数に NULL を渡したら問題が起きると思いますが、そのように呼び出している箇所は無いと思います。

という事で、それについては対応してもしなくてもどちらでも良いとの判断で Approve します。

Copy link
Contributor

@berryzplus berryzplus left a comment

Choose a reason for hiding this comment

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

Security Hotspotが検出されないように修正を試みてほしいです。

@Ocelot1210
Copy link
Contributor Author

ご指摘ありがとうございます
wcslen が Security Hotspot として検出されるとは思わず、お手数をおかけして申し訳ございません...

以下対応いたしました

  • ご提示頂いた方法で Security Hotspot が検出されないように修正を試みた
  • SelectDir 関数冒頭で出力先バッファの nullptr チェックを追加

@sonarcloud
Copy link

sonarcloud bot commented Apr 10, 2021

@AppVeyorBot
Copy link

@berryzplus berryzplus dismissed their stale review April 10, 2021 09:35

Security Hotspotの解消を確認したので。

Copy link
Contributor

@berryzplus berryzplus left a comment

Choose a reason for hiding this comment

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

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

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.

動作確認して特に問題は見つかりませんでした。

@beru beru merged commit ab89e26 into sakura-editor:master Apr 10, 2021
@Ocelot1210 Ocelot1210 deleted the use_ifiledialog branch April 12, 2021 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
specification change ■仕様変更
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants