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

メモリバッファクラスをx64対応できるように書き替える #1618

Merged

Conversation

berryzplus
Copy link
Contributor

@berryzplus berryzplus commented Apr 8, 2021

PR の目的

CMemoryをリファクタリングして、x64対応作業を始められるようにすることが目的です。

カテゴリ

  • その他

PR の背景

CMemoryやCNativeWはサクラエディタの中核に位置するクラスです。

SonarCloudによる静的解析で、以下のBugsレベル警告が検出されています。

これらのクラスは、依存関係のルート付近にあるヘッダーで定義されているため、修正がしづらいです。
最近マージされた #1615 により、修正のやりづらさはさらに増したと思います。

ただ、ここを修正しないと x64対応 は不可能なので、どこかでなんとかしたいと考えていました。

今回は、思い切ってCMemory/CNativeWの大幅改修をして、x64対応の作業ができる状態を作り出したいと思います。

PR のメリット

  • 上記3件のBugsレベル警告を解消できます。
  • 現在発生している2万件のCodeSmellsのうち21件を解消できます。
  • CMemoryの単体テスト網羅率が100%になります。
  • INT_MAX(=約2GB) を超えるメモリの確保を試みた場合に確実に「失敗」するようになります。
    ※従来は異常を検出できず、処理が続行されていました。
  • x64 - Debugビルドの警告が 576件 から 497件 に減ります。

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

  • CMemoryのpublicメソッドをいくつかリネームしているため、修正量が多くなっています。
  • CodeSmellsが新たに18件増えます。
    • うち2件は原因が別クラスの定義にあるので対応不要です。
    • うち7件はCMemoryクラスの仕様的に対応不可です。
    • 残り9件は対応が必要と考えますが、今回は対応しません。

仕様・動作説明

サクラエディタの仕様・機能に変更はありません。

CMemoryで定義されているメソッドの改廃を行っています。

クラス名 メソッド名 対応 代替 説明 対応理由
CMemory Clean 削除 Reset バッファをリセット(解放)する 同機能の別名メソッドがあり、存在自体が冗長であるため。
CMemory Clear 削除 Reset バッファをリセット(解放)する CNativeの同名メソッドと混同する危険があるため。
CMemory _Empty 削除 Reset バッファをリセット(解放)する メモリバッファのリセット(解放)はクラス外部から普通に利用する機能であり、非公開にしておく意味がないため。
CMemory Reset 新規 - バッファをリセット(解放)する リセット(解放)を意味するメソッド名が存在していなかったため。
CMemory _AddData 削除 AppendRawData データを追加する メモリ確保とコピーを統合することにより、メモリ確保失敗時にコピーが強行される事態を防ぐ必要があった。
CMemory SwabHLByte 削除 SwapHLByte Byteを交換する Windows SDK_swabを利用するためには、好ましくないキャストをする必要がある。加えて、_swabを利用するメリットはとくにないため。

PR の影響範囲

  • CMemoryを利用する機能(エディタ機能全般)に影響する修正です。

テスト内容

影響範囲が広すぎるので、単純な起動確認をしたほうが良さそうです。

  1. サクラエディタを起動する。
  2. 任意のテキストファイルを開く。

関連 issue, PR

#1504
#1541
#1575
#1605

参考資料

@AppVeyorBot
Copy link

Build sakura 1.0.3646 failed (commit e869c6f2d4 by @berryzplus)

@AppVeyorBot
Copy link

Build sakura 1.0.3647 failed (commit 0fdb081204 by @berryzplus)

サイズ変数をsize_t化した際の修正が不十分でVA発生いたため、追加修正
@AppVeyorBot
Copy link

@berryzplus berryzplus marked this pull request as ready for review April 9, 2021 01:53
sakura_core/mem/CNativeA.cpp Outdated Show resolved Hide resolved
sakura_core/mem/CNativeA.cpp Outdated Show resolved Hide resolved
sakura_core/mem/CNativeW.cpp Outdated Show resolved Hide resolved
sakura_core/mem/CNativeW.cpp Outdated Show resolved Hide resolved
sakura_core/mem/CNativeW.cpp Outdated Show resolved Hide resolved
sakura_core/mem/CNativeW.cpp Outdated Show resolved Hide resolved
sakura_core/mem/CNativeW.cpp Outdated Show resolved Hide resolved
@berryzplus berryzplus marked this pull request as draft April 9, 2021 03:33
@AppVeyorBot
Copy link

@berryzplus
Copy link
Contributor Author

こんなの出てたので追加修正しました。

Rename this member function so that it doesn't hide an inherited non-virtual function, or make it virtual in the base class "CMemory".
https://sonarcloud.io/project/issues?id=sakura-editor_sakura&issues=AXi037PsqNOaEhErIvC_&open=AXi037PsqNOaEhErIvC_&pullRequest=1618

@AppVeyorBot
Copy link

Build sakura 1.0.3650 failed (commit f2d01f74b0 by @berryzplus)

@AppVeyorBot
Copy link

@berryzplus
Copy link
Contributor Author

#1618 (comment) を対応しようとしたらこんなの出たので諦めます。

'CMemory' has virtual functions but non-virtual destructor
https://sonarcloud.io/project/issues?id=sakura-editor_sakura&issues=AXi1t3KSmnSiw_EpU5Ks&open=AXi1t3KSmnSiw_EpU5Ks&pullRequest=1618

@sonarcloud
Copy link

sonarcloud bot commented Apr 9, 2021

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 10 Code Smells

87.3% 87.3% Coverage
0.0% 0.0% Duplication

@AppVeyorBot
Copy link

@berryzplus berryzplus marked this pull request as ready for review April 9, 2021 12:05
CMemory cmem( pData, nDataLen );
cmem.SwapHLByte();
if( cmem.GetRawPtr() != nullptr ){
::memcpy( pData, cmem.GetRawPtr(), nDataLen );
Copy link
Member

Choose a reason for hiding this comment

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

元のコードが過去にパフォーマンス問題に対策した結果のものに見えるので、115行目のコンストラクタと memcpy での2回のコピーがパフォーマンスに与える影響が気になりました。ビット演算するのが書いた人の趣味だった可能性もありますけれど…。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

bswapはCPU命令なので、パフォーマンスを重視するならこちらを使うべきです。

もともとはstaticバージョンに本体があり、インスタンスメソッド側が参照する構造になっていたのを逆転しています。staticバージョンを使わない方向に持っていき、最終的には廃止したいと考えているので、あえて不都合が起きる実装にしています。いきなり動かなくすると困りそうなのでいったん残してある状態です。

Copy link
Member

Choose a reason for hiding this comment

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

過渡的な実装ということで承知しました。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ちなみにstaticバージョンでメモリコピーを行っている理由は、以下です。

  • bswapの対象はuint16_t(=unsigned short)です。
    • uint16_t*の値は、必ず偶数アドレスになります。
  • static関数の引数はchar*です。
    • char*の値は、奇数アドレスになりえます。

 👇

  • bswapを使うには、char*uint16_t*に reinterpret キャストする必要があるので、問題をふせぐために新たに確保したメモリにコピーしてから処理します。

細かいことをいうと、mallocが返すアドレス値がどんな値であるか(≒偶数or奇数、アラインメント単位は?)について言語仕様的な規定はないはずです。返却されるメモリアドレスになんらかの要求仕様があるときには _aligned_malloc を使って仕様を明示します。一応、mallocで奇数アドレスが返ってきたのを見たことありませんし、現状の通常mallocで問題はないと思っていますが。

p[1] = ctemp;
// 既に必要サイズを確保できている場合、直ちに抜ける
if( nAllocSize < m_nDataBufSize ){
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

不具合ではないのですが、<よりも<=のほうがほんの少しだけ幸せになるかもしれないと思いました。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ここを less than にした理由は capacity() の実装に起因しています。
おっしゃる通りだと思うのでどこかで対応したいです。(今回は見送りたいです)

void _SetRawLength( size_t nLength );
void swap( CMemory& left ) noexcept;
//! メモリ再確保を行わずに格納できる最大バイト数を求める
[[nodiscard]] int capacity() const noexcept { return 8 <= m_nDataBufSize ? m_nDataBufSize - 2: 0; }
Copy link
Contributor

Choose a reason for hiding this comment

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

この8というマジックナンバーが曲者ですね。
2を引くのはm_nDataBufSizeがNUL終端を含んでいるからというのが根拠ですが、
8のほうはAllocBufferの実装に強く依存している可能性があるというか。
マイナス値を避けたいので変更したんだと思いますが、
2以上であれば、8でなくてもよいというなら、2のほうがいいかもしれないとは思います。
(これは修正要求ではないので、変更しなくてもかまいません)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

マジックナンバー「8」についてはクラスコメントに記載しました。
「内部バッファのサイズは8の倍数に切り上げて確保される。」
8を定数化するのがあるべき姿かもしれません。
(冷静に考えると、0より1つ大きい値が8なので「0 < m_nDataBufSize」にしとけばいいのかも。)

現在のcapacity()実装は標準ライブラリstd::wstring::capacity()と異なるので、メソッドをリネームするか、実装を標準ライブラリに寄せるかして取り違え防止を図るのがよいと思います。

2 = sizeof(wchar_t) だから実質的に「1つ違いエラー」の見直しになるので今回PR対象からは外しました。

@berryzplus
Copy link
Contributor Author

レビューありがとうございます。とりあえずマージしちゃいます。

このPRは目的が「準備」であることもあり、至らぬ点が多数ある認識っす。

何か気付いた点があれば、このPRのコメントでも良いですし、新規issueでも良いので課題をあげてもらえれば幸いです。

@berryzplus berryzplus merged commit f62f209 into sakura-editor:master Apr 11, 2021
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.

None yet

5 participants