今日の記事は少し変わっています。 少なくとも1つのプロジェクトを分析するのではなく、一度に3つのプロジェクトでエラーを探し、最も興味深いバグがある場所を確認するという理由で。 そして最も興味深いのは、誰が若くて最高品質のコードを書いているかを知ることです。 そのため、議題には、Firebird、MySQL、およびPostgreSQLプロジェクトのコードのエラーの分析があります。
プロジェクトについて簡単に
火の鳥

Firebird(FirebirdSQL)は、Mac OS X、Linux、Microsoft Windows、およびさまざまなUnixプラットフォームで実行されるクロスプラットフォームデータベース管理システム(DBMS)です。
Firebirdは2001年以来、さまざまな産業システム(倉庫およびビジネス、金融、公共部門)で使用されています。CおよびC ++プログラマー、技術顧問の商業的に独立したプロジェクトです。
追加情報:MySQL

MySQLは無料のリレーショナルデータベース管理システムです。 通常、MySQLはローカルまたはリモートクライアントからアクセスされるサーバーとして使用されますが、ディストリビューションには、MySQLをスタンドアロンプログラムに含めることができる内部サーバーライブラリが含まれています。
MySQL DBMSの柔軟性は、多数のテーブルタイプをサポートすることにより提供されます。ユーザーは、フルテキスト検索をサポートするMyISAMテーブルと、個々のレコードレベルでトランザクションをサポートするInnoDBテーブルの両方を選択できます。 さらに、MySQL DBMSには特別なタイプのテーブル例が付属しており、新しいタイプのテーブルを作成する原理を示しています。 オープンアーキテクチャとGPLライセンスのおかげで、新しいタイプのテーブルが常にMySQL DBMSに登場しています。
追加情報 :
PostgreSQL

PostgreSQLは無料のオブジェクトリレーショナルデータベース管理システム(DBMS)です。
AIX、さまざまなBSDシステム、HP-UX、IRIX、Linux、macOS、Solaris / OpenSolaris、Tru64、QNX、Microsoft Windowsなど、多くのUNIXライクなプラットフォームの実装があります。 小規模な個人用アプリケーションから多数の同時ユーザーを含む大規模なインターネットアプリケーション(データウェアハウス)まで、さまざまな量のデータに対応できます。
PostgreSQLは、カリフォルニア大学バークレー校でオープンソースプロジェクトとして開発された非営利のPostgres DBMSに基づいています。
追加情報 :
PVS-Studio

エラーを見つける手段として、
PVS-Studio静的コードアナライザーが使用されました。 PVS-Studioは、C、C ++、C#プログラミング言語用のソースコードアナライザーであり、プログラムコードのエラー、欠陥、潜在的な脆弱性を早期に検出することにより、ソフトウェア開発コストを削減します。 WindowsおよびLinux環境で動作します。
ダウンロードリンク:
3つのプロジェクトはすべてアセンブルされ、.slnファイルが含まれている(すぐに、またはCMakeを介して生成された)ため、分析のタスク自体は完全に簡単になります-Visual Studio IDEに組み込まれたPVS-Studioプラグインインターフェイスを介してテストを実行するだけです。
比較基準
プロジェクトで興味深いと思われるものを見る前に、記事の主な質問の1つ、つまり比較を実行する基準によって決定する必要があります。
直接比較が良い考えではないのはなぜですか?
アナライザーによって発行された警告の数(または、コードの行数に対する警告の数の比率)を真正面から比較することは、最も労力のかからない方法ですが、良い考えではありません。 なんで? PostgreSQLプロジェクトを例に取ります。ここでは、信頼性の高いGA警告の数は611です
。PVS -Studioウィンドウで診断ルールコード(
V547 )とメッセージの一部(
ret <0 )によるフィルタリングを設定すると、419個の警告が表示されます! 多すぎる...これは、マクロやコードが自動的に生成されるなど、これらの警告の原因があることをすぐに示唆しています。 これらの警告を含むファイルの冒頭のコメントは、理論を裏付けています。
コードが自動的に生成されたことを知った今、2つの方法があります。
- 自動生成されたコードは重要ではないため、すべての警告を抑制します。 したがって、警告(GA、Lvl1)の数はすぐに69%減少します!
- 自動生成されたコードのエラーがまだエラーであることを受け入れ、それについて何かを試みます(たとえば、コード生成スクリプトを修正します)。 この場合、警告の数は引き続き重要です。
別の落とし穴は、プロジェクトで使用されるサードパーティコンポーネントのエラーです。 再び:
- そのような間違いはあなたの頭痛ではないと言えます。 ユーザーはこの声明に同意しますか?
- 責任を取ります。
これらは、選択の問題を引き起こす可能性のあるほんの2、3の例であり、その解決策は関連する警告の数を(場合によっては大幅に)変更できます。
別の方法
第3レベルの信頼性(低い確実性)の警告は考慮されないことにすぐに同意します。 これらは、最初に注意する必要があるものではありません。 間違いなく有用なものがあるかもしれませんが、記事を書くときや静的分析を使用するときは、レベル3の警告を無視するのが理にかなっています。
この作業は多くの理由で非常に労働集約的であるため、本格的な比較は行いません。 プロジェクトごとに少なくとも予備のアナライザー設定を取得し、数百の警告を表示および分析します。これには非常に長い時間がかかりますが、効率はどうなるでしょうか。これは未解決の問題です。
したがって、私たちは別の方法で行動します。 3つのプロジェクトすべてのログを調べ、最も興味深いエラーのいくつかを見つけて解析し、他のプロジェクトにそのような何かがあるかどうかを同時に調べます。
さらに、比較的最近、セキュリティ問題の検索の方向に目を向け始めました。 記事でさえこのトピックに関するものでした-「
PVS-Studioが脆弱性を見つけるのにどのように役立つか 」。 このレビューの参加者の1人であるMySQLが上記の記事に参加したことを考えると、同様の動きを検出できるかどうかを確認することに興味がありました。 トリックはありません-脆弱性に関する記事の警告と同様に、PVS-Studioの警告を追加で確認してください。

上記を要約して、次の基準に従ってコードの品質を評価します。
- 前述の脆弱性に関する記事からアナライザーの警告番号を取得し、3つのプロジェクトすべてで同様の警告を探します。 このアプローチは理解できると思います-そのようなコードは(常にではありませんが)脆弱性である可能性があることが知られているため、特別な注意を払う価値があります。
- 最初の2つの信頼レベルのアナライザーのGA警告を見て、最も興味深いと思われるものを選択し、他のプロジェクトでこのようなものがあるかどうかを確認します。
これらのチェックの結果に基づいて、プロジェクトの貯金箱にペナルティポイントを記録します。 したがって、より少ないポイントを獲得した人は、上記のアプローチに関連して最高のコードを持っています。 もちろん、微妙な違いはありますが、分析として、また要約するときに説明します。
それでは、始めましょう!
解析エラー
一般的な分析結果
以下の表は、「現状のまま」で行われたプロジェクトの分析の一般的な結果を示しています-誤った警告、ディレクトリによるフィルタリングなどを抑制しません。 これらは一般的な警告にすぎないことに注意してください。
プロジェクト
| 高い確実性
| 中程度の確実性
| 低い確か
| 合計
|
火の鳥
| 156
| 680
| 1045
| 1881
|
MySQL
| 902
| 1448
| 2925
| 5275
|
PostgreSQL
| 611
| 1432
| 1576
| 3619
|
ただし、この表のコードの品質を判断しないでください。 上記の理由を述べましたが、繰り返します:
- アナライザーの予備セットアップの欠如;
- 誤検知は抑制されません。
- コードベースの異なるサイズ。
- 記事の執筆中にアナライザーに修正が加えられたため、執筆の開始時と終了時にこの表の結果が異なる場合があります。
警告の密度(エラーではありません!)に関しては、アナライザーの事前設定なしで取得されます。つまり、LOCに対する警告の数の比率です。FirebirdとPostgreSQLではほぼ等しく、MySQLではわずかに高くなります。 しかし、あなたが知っているように、悪魔は細部にあるので、私たちは急いで結論を下しません。
個人データの上書きの問題
警告
V597は、最適化中にコンパイラーによって削除できるデータクリーニングを実行する
memset関数の呼び出しの存在を通知します。 このため、個人データはクリーンアップされないままになる場合があります。 問題の詳細については
、診断ルールのドキュメントを参照してください 。
FirebirdもPostgreSQLもそのような警告を表示しませんでしたが、MySQLについては言えません。 このプロジェクトの疑わしいコードを見てみましょう。
extern "C" char * my_crypt_genhash(char *ctbuffer, size_t ctbufflen, const char *plaintext, size_t plaintext_len, const char *switchsalt, const char **params) { int salt_len; size_t i; char *salt; unsigned char A[DIGEST_LEN]; unsigned char B[DIGEST_LEN]; unsigned char DP[DIGEST_LEN]; unsigned char DS[DIGEST_LEN]; .... (void) memset(A, 0, sizeof (A)); (void) memset(B, 0, sizeof (B)); (void) memset(DP, 0, sizeof (DP)); (void) memset(DS, 0, sizeof (DS)); return (ctbuffer); }
PVS-Studioの警告 :
- V597コンパイラーは、「A」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 crypt_genhash_impl.cc 420
- V597コンパイラーは、「B」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 crypt_genhash_impl.cc 421
- V597コンパイラーは、「DP」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 crypt_genhash_impl.cc 422
- V597コンパイラーは、「DS」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 crypt_genhash_impl.cc 423
アナライザーは、1つの関数(!)で4つのバッファーを即座に検出しました。どのデータに対して強制的なデータクリーニングを実行する必要があり、同時に発生しない可能性があります。 無効化された(理論上)データは、「そのまま」の形でメモリに残ります。 バッファ
A 、
B 、
DP 、
DSのさらなる使用がないため、コンパイラは
memset関数呼び出しを削除できます。そのような変更は、C / C ++の観点からプログラムの動作に影響を与えないためです。 この問題の詳細については、「
プライベートデータの安全なクリーニング 」を参照してください。
他の警告も同様なので、分解しません。 それらをリストします。
- V597コンパイラーは、「table_list」オブジェクトのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 sql_show.cc 630
- V597コンパイラーは、「W」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 sha.cpp 413
- V597コンパイラーは、「W」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 sha.cpp 490
- V597コンパイラーは、「T」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 sha.cpp 491
- V597コンパイラーは、「W」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 sha.cpp 597
- V597コンパイラーは、「T」バッファーのフラッシュに使用される「memset」関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 sha.cpp 598
そして、もう少し興味深いケースがあります。
void win32_dealloc(struct event_base *_base, void *arg) { struct win32op *win32op = arg; .... memset(win32op, 0, sizeof(win32op)); free(win32op); }
PVS-Studio警告 :
V597コンパイラは、 'win32op'オブジェクトのフラッシュに使用される 'memset'関数呼び出しを削除できました。 RtlSecureZeroMemory()関数を使用して、プライベートデータを消去する必要があります。 win32.c 442
ここでも状況は似ていますが、メモリ内のデータをゼロにした後、対応するポインターが
free関数に渡されます。 それにもかかわらず、コンパイラーは
memset呼び出しを削除して、関数の呼び出しだけを残すことができます。 その結果、ゼロにリセットする必要があるデータがメモリに残る場合があります。 詳細については、上記の記事をご覧ください。
得点 かなり重大な間違いです。1つのコピーには見られないものがあります。 3 MySQLペナルティポイント。
mallocおよび同様の関数によって返されるポインターの検証の欠如
V769警告は、3つのプロジェクトすべてに対して発行されました。
- Firebird:高い確実性-0; 中程度の確実性-0; 低い確実性-9;
- MySQL:確実性が高い-0; 中程度の確実性-13; 低い確実性-103;
- PostgreSQL:高確実性-1中確実性-2; 低い確実性-24。
3番目のレベルを考慮しないことに同意したため、Firebirdはすぐに(良い意味で)比較から除外されます。 PostgreSQLコードに関する3つの警告もすべて無関係であることが判明しました。 しかし、MySQLでは、すべてがそれほど明確ではありません。 誤検知もありましたが、いくつかの警告は非常に興味深いものです。
bool Gcs_message_stage_lz4::apply(Gcs_packet &packet) { .... unsigned char *new_buffer = (unsigned char*) malloc(new_capacity); unsigned char *new_payload_ptr = new_buffer + fixed_header_len + hd_len;
PVS-Studio 警告 :
V769 「new_buffer + fixed_header_len」式の「new_buffer」ポインターはnullptrである可能性があります。 そのような場合、結果の値は無意味になり、使用しないでください。 行をチェック:74、73。gcs_message_stage_lz4.cc 74
malloc関数は、要求されたメモリブロックを返すことができなかった場合、
new_buffer変数に書き込むことができるnullポインタを返します。 さらに、
new_payload_ptr変数の値を初期化するとき、
new_bufferポインターの値が
fixed_header_lenおよび
hd_len変数の値に追加され
ます 。 それだけです、ポインター
new_payload_ptrが返されないポイント:さらにどこかで(たとえば、別の関数で)
NULLと比較してポインターの有効性をチェックしたい場合、そのようなチェックは役に立ちません。 結果を自分で判断できます。 したがって、
new_payload_ptrを初期化する前に、
new_bufferがNULLポインターではないことを確認する
必要があります。
誰かが反対する可能性があります-必要なメモリブロックを取得できなかった場合、返された
malloc値の
NULLをチェックする理由 とにかく、それ以上の通常の操作は不可能です。したがって、たとえば、このポインターを使用してさらに作業を行うと、アプリケーションがクラッシュします。
一部の開発者はこの立場に固執しているため、存在する権利がありますが、このアプローチはどの程度正しいのでしょうか? 結局、同様の状況を何らかの方法で処理して、たとえば、データを失ったり、「より穏やかに」落ちたりしないようにすることができます。 さらに、そのようなコードは潜在的に脆弱になります。 nullポインターでは作業が直接行われず、別のメモリブロック(
nullポインター+ value )で作業が発生する場合、アプリケーションは一部のデータを損傷する可能性があります。 さらに、これはすべて、アプリケーションに脆弱性を追加する別の方法です。 必要ですか? 長所、短所、最終決定は、誰もが自分で行うと思います。
2番目のアプローチに従うことをお勧めします。診断ルール
V769は、このような状況の検出に役立ちます。
そのような関数が
NULLを決して返すことができないと判断した場合、適切な警告を受け取らないように、これについてアナライザーに通知できます。 これを行う方法は、記事「
高度な診断セットアップ 」で説明されてい
ます 。
得点 上記を考慮すると、MySQLは1ペナルティポイントを受け取ります。
潜在的にヌルのポインターを使用する
V575アラートは、3つのプロジェクトすべてに対して発行されています。
Firebirdプロジェクトのエラー例(中程度の確実性):
static void write_log(int log_action, const char* buff) { .... log_info* tmp = static_cast<log_info*>(malloc(sizeof(log_info))); memset(tmp, 0, sizeof(log_info)); .... }
PVS-Studio警告 :
V575潜在的なヌルポインターが 'memset'関数に渡されます。 最初の引数を調べます。 行を確認してください:1106、1105。iscguard.cpp 1106
問題は上記の問題と似ています
-malloc関数の戻り値はチェックされません。 要求された量のメモリを割り当てることができなかった場合、
mallocはnullポインタを返し、それが
memset関数に渡されます。
MySQLプロジェクトの同様のコード:
Xcom_member_state::Xcom_member_state(....) { .... m_data_size= data_size; m_data= static_cast<uchar *>(malloc(sizeof(uchar) * m_data_size)); memcpy(m_data, data, m_data_size); .... }
PVS-Studio警告 :
V575潜在的なヌルポインターが 'memcpy'関数に渡されます。 最初の引数を調べます。 行をチェック:43、42。gcs_xcom_state_exchange.cc 43
このエラーは、上記のFirebirdの問題に似ています。 念のため、返された
malloc値の
NULL不等式がチェックされる場所があることを思い出します。 しかし、これは彼らには当てはまりません。
PostgreSQLも同様のコードを見つけました。
static void ecpg_filter(const char *sourcefile, const char *outfile) { .... n = (char *) malloc(plen); StrNCpy(n, p + 1, plen); .... }
PVS-Studio警告 :
V575潜在的なヌルポインターが 'strncpy'関数に渡されます。 最初の引数を調べます。 行をチェック:66、65。pg_regress_ecpg.c 66
ただし、MySQLおよびPostgreSQLプロジェクトの確実性レベルが高いというより興味深い警告がありました。
MySQLのコードスニペット:
View_change_event::View_change_event(char* raw_view_id) : Binary_log_event(VIEW_CHANGE_EVENT), view_id(), seq_number(0), certification_info() { memcpy(view_id, raw_view_id, strlen(raw_view_id)); }
PVS-Studio警告 :
V575 「memcpy」関数は文字列全体をコピーしません。 端末のヌルを保持するには、「strcpy / strcpy_s」関数を使用します。 control_events.cpp 830
memcpy関数を使用して、文字列を
raw_view_idから
view_idにコピーします。コピーされたバイト数は、
strlen関数を使用して計算されます。 ニュアンスは、
strlenが終端のゼロを考慮せずに文字列の長さを返すため、コピーされないことです。 ターミナルゼロを自分で追加しないと、文字列を操作するための関数が
view_idで正しく機能しないことに
注意してください 。 文字列を正しくコピーするには、
strcpy /
strcpy_s関数を
使用する必要があります。
PostgreSQLの同様のコードのように見えます。
static int PerformRadiusTransaction(char *server, char *secret, char *portstr, char *identifier, char *user_name, char *passwd) { .... uint8 *cryptvector; .... cryptvector = palloc(strlen(secret) + RADIUS_VECTOR_LENGTH); memcpy(cryptvector, secret, strlen(secret)); }
PVS-Studio警告 :
V575 「memcpy」関数は文字列全体をコピーしません。 端末のヌルを保持するには、「strcpy / strcpy_s」関数を使用します。 auth.c 2956
前のケースとは興味深い違いがあります。
cryptvector変数の
タイプは
uint8 *です。
uint8は
unsigned charのエイリアスであるという事実にもかかわらず、データが文字列のように機能しないことを示すために明示的な意図が表現されているように思えます。 したがって、このコンテキストでは、このような操作は許可され、前の操作のように警告されません。
確かに、安全性が低いと思われるコードにも遭遇しました。
int intoasc(interval * i, char *str) { char *tmp; errno = 0; tmp = PGTYPESinterval_to_asc(i); if (!tmp) return -errno; memcpy(str, tmp, strlen(tmp)); free(tmp); return 0; }
PVS-Studio警告 :
V575 「memcpy」関数は文字列全体をコピーしません。 端末のヌルを保持するには、「strcpy / strcpy_s」関数を使用します。 informix.c 677
上記の状況と似ていますが、MySQLのコードに近い-文字列が使用され、その内容(ターミナルゼロを除く)は外部のどこかで使用されるメモリにコピーされます...
得点 Firebird-1ペナルティポイント、PostgreSQLおよびMySQL-3ペナルティポイント、(1-中程度の信頼レベルの警告、2-高信頼レベルの場合)。
書式設定関数の潜在的に危険な使用
V618警告は、Firebirdプロジェクトのコードに対してのみ発行されました。
例を考えてみましょう:
static const char* const USAGE_COMP = " USAGE IS COMP"; static void gen_based( const act* action) { .... fprintf(gpreGlob.out_file, USAGE_COMP); .... }
PVS-Studio警告 :
V618このような方法で 'fprintf'関数を呼び出すのは危険です。渡される行に形式の仕様が含まれている可能性があるためです。 安全なコードの例:printf( "%s"、str); cob.cpp 1020
アナライザーは、フォーマットされた出力(
fprintf )の関数が使用されていることを警告しましたが、同時に、対応する修飾子を持つフォーマット文字列を使用せずに、行が直接印刷されました。 これは危険な場合があり、印刷された行に形式指定子が見つかった場合に脆弱性(
CVE-2013-4258を参照)を引き起こすことさえあります。 ここでは、
USAGE_COMP行
はソースコードで明示的に定義されており、形式指定子を含んでいないため、そのような使用は有効と見なすことができます。
他の場所では、状況は似ています。印刷された行はハードコーディングされており、形式指定子を含んでいませんでした。
得点 上記の内容を考慮して、Firebirdを「罰金」しないことにしました。
脆弱性に関する記事のその他の警告
プロジェクトのV642および
V640警告は発行されませんでした-すべてよくやった。
列挙要素の疑わしい使用
MySQLのサンプルコード。
enum wkbType { wkb_invalid_type= 0, wkb_first= 1, wkb_point= 1, wkb_linestring= 2, wkb_polygon= 3, wkb_multipoint= 4, wkb_multilinestring= 5, wkb_multipolygon= 6, wkb_geometrycollection= 7, wkb_polygon_inner_rings= 31, wkb_last=31 }; bool append_geometry(....) { .... if (header.wkb_type == Geometry::wkb_multipoint) .... else if (header.wkb_type == Geometry::wkb_multipolygon) .... else if (Geometry::wkb_multilinestring) .... else DBUG_ASSERT(false); .... }
PVS-Studio警告 :
V768列挙定数 'wkb_multilinestring'はブール型の変数として使用されます。 item_geofunc.cc 1887
原則として、警告テキストはそれ自体を物語っています。 条件式を見ると、2つは
header.wkb_typeと
Geomerty列挙
要素の比較であり、3番目の条件式全体が列挙
要素であることが
わかります。
Geometry :: wkb_multilinestringの値は
5であるため、この2つのチェックが失敗すると、この条件ステートメントの本体が常に実行されます。 したがって、
DBUG_ASSERTマクロを含む
elseブランチはまったく実行されません。 明らかに、3番目の条件式の正しい形式は次のとおりです。
header.wkb_type == Geometry::wkb_multilinestring
他のプロジェクトはどうですか? PostgreSQLでは、そのような警告はありませんでしたが、Firebirdでは9件もあります。実際、これらの警告はすでに下のレベル(中程度の確実性)にあり、検出されたパターンも異なります。
V768診断ルールによって検出されたエラーの検索パターンは次のとおりです。
- 高い確実性:列挙型メンバーを論理型式として使用します。
- 中程度の確実性:列挙型の変数を論理型の式として使用します。
したがって、最初のケースでうまくいかない場合でも、2番目の信頼レベルでアナライザーの警告と何らかの形で議論することができます。
たとえば、ほとんどの場合は次のようなものです。
enum att_type { att_end = 0, .... }; void fix_exception(...., att_type& failed_attrib, ....) { .... if (!failed_attrib) .... }
PVS-Studio警告 :
V768変数「failed_attrib」は列挙型です。 ブール型の変数として使用されるのは奇妙です。 restore.cpp 8580
アナライザーは、
failed_attrib変数の値が
att_type :: att_endであることを確認するコードが疑わしいと
見なしました 。 たとえば、列挙要素との明示的な比較を好むでしょう。 ただし、このコードが間違っているとは言えません。 はい、このスタイル(およびアナライザーも)は好きではありませんが、コードは有効です。
しかし、やや不審に見える場所が2つあります。 パターンは同じなので、1つのケースのみを検討してください。
namespace EDS { .... enum TraScope {traAutonomous = 1, traCommon, traTwoPhase}; .... } class ExecStatementNode : .... { .... EDS::TraScope traScope; .... }; void ExecStatementNode::genBlr(DsqlCompilerScratch* dsqlScratch) { .... if (traScope) .... .... }
PVS-Studio警告 :
V768変数 'traScope'は列挙型です。 ブール型の変数として使用されるのは奇妙です。 stmtnodes.cpp 3448
コードは前のコードに似ています-彼らはまた、
traScope変数に実際の非ゼロ値を持つ列挙要素の値が含まれていることを確認したかったのです。 ただし、ここでは、前の例とは異なり、実際の値が「0」の列挙要素はありません。 したがって、このコードは前のコードよりも疑わしいように見えます。
平均レベルの信頼度の警告について話しているので、それらがMySQLでも見つかったことを追加する価値があります-10個。
得点 Firebirdは、ペナルティポイント1つ、MySQL-2を受け取ります。
不正なメモリブロックサイズの計算
ところで、ここにもう1つの興味深いコードがあります。さらに、メモリ内のプライベートデータの上書きを処理する際に、すでに以前に彼に目を向けました。 struct win32op { int fd_setsz; struct win_fd_set *readset_in; struct win_fd_set *writeset_in; struct win_fd_set *readset_out; struct win_fd_set *writeset_out; struct win_fd_set *exset_out; RB_HEAD(event_map, event_entry) event_root; unsigned signals_are_broken : 1; }; void win32_dealloc(struct event_base *_base, void *arg) { struct win32op *win32op = arg; .... memset(win32op, 0, sizeof(win32op)); free(win32op); }
PVS-Studio警告:V579 memset関数は、ポインターとそのサイズを引数として受け取ります。間違いかもしれません。 3番目の引数を調べます。
win32.c 442 memset関数呼び出しの3番目の引数に注意してください。sizeof演算子は引数のサイズをバイト単位で返しますが、この場合、引数はポインターです。したがって、sizeof演算子は構造体のサイズではなく、ポインターのサイズを返します。このため、memset関数呼び出しが削除されない場合でも、メモリサイズは必要量よりも少なくなります。道徳-変数名を慎重に選択し、混乱しやすい変数名を避けてください。そのような名前なしではできないこともありますが、そのような場合は二重に注意する必要があります。多くのエラーがこれに関連付けられており、V501診断ルールを使用して検出できます。でC / C ++プロジェクトおよびV3001でのC#プロジェクト。他のプロジェクトでは、V579の警告は見つかりませんでした。得点 MySQLの2ペナルティポイント。同様のエラーがありました。再びMySQLで。 typedef char Error_message_buf[1024]; const char* get_last_error_message(Error_message_buf buf) { int error= GetLastError(); buf[0]= '\0'; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)buf, sizeof(buf), NULL ); return buf; }
PVS-Studio 警告:V511 sizeof()演算子は、配列のサイズではなく、ポインターのサイズを 'sizeof(buf)'式で返します。common.cc 507Error_message_buf - char型の1024要素の配列のエイリアス。1つの重要な点に留意してください-関数のシグネチャが次の場合でも const char* get_last_error_message(char buf[1024])
bufはポインターであり、その後に生じるすべての結果を伴います。配列のサイズはプログラマーへのヒントにすぎません。したがって、上記のコードフラグメントでは、sizeof(buf)式は配列ではなくポインターで機能しています。その結果、関数は間違ったバッファサイズを受け取ります-1024ではなく4または8。FirebirdとPostgreSQLでは、同様の警告は再びありませんでした。得点 MySQLの2つのペナルティポイント。スローキーワードがありません
もう一つの興味深い間違い、そして今回は...再びMySQLから。コードフラグメントは小さいため、全体を示します。 mysqlx::XProtocol* active() { if (!active_connection) std::runtime_error("no active session"); return active_connection.get(); }
PVS-Studio警告:V596オブジェクトは作成されましたが、使用されていません。「throw」キーワードが欠落している可能性があります:throw runtime_error(FOO); mysqlxtest.cc 509 std :: runtime_errorクラスのオブジェクトが作成されますが、使用されません。明らかに、例外はスローされることを意図していましたが、プログラマはthrowキーワードを指定するのを忘れていました。その結果、例外(active_connection == nullptr)は期待どおりに処理されません。FirebirdもPostgreSQLもそのような警告はありませんでした。得点 MySQLの2つのペナルティポイント。Invalid Memory Freeステートメントの呼び出し
次のコードサンプルは、Firebirdプロジェクトから取得したものです。 class Message { .... void createBuffer(Firebird::IMessageMetadata* aMeta) { unsigned l = aMeta->getMessageLength(&statusWrapper); check(&statusWrapper); buffer = new unsigned char[l]; } .... ~Message() { delete buffer; .... } ..... unsigned char* buffer; .... };
PVS-Studio 警告:V611メモリは「new T []」演算子を使用して割り当てられましたが、「delete」演算子を使用して解放されました。このコードを調べることを検討してください。
「delete [] buffer;」を使用することをお勧めします。ラインチェック:101、237 message.h 101(ポインタによって参照されるバッファメモリバッファ' -のクラスフィールドのメッセージが)特別な方法で割り当てられる- createBuffer予想通り、そして、それはオペレータ使用新規の[] 。しかし、クラスデストラクタは、オペレータによって使用されるメモリを解放するために削除する代わりに]、削除[。MySQLおよびPostgreSQLにはそのようなエラーはありませんでした。得点 Firebirdの2ペナルティポイント。まとめると
ペナルティポイントを合計すると、次の結果が得られます。- Firebird:1 + 1 + 2 = 4ポイント。
- MySQL:3 + 1 + 2 + 2 + 2 + 2 = 12ポイント。
- PostgreSQL:3ポイント。
ポイントが少ないほど良いことを思い出します。そして、ここで私(甘やかされた好みを持つ人)が最も好きだった... MySQL!それには最も興味深いエラーが含まれていて、その場所でもすべてが明確です-ここにあるのは、分析のための理想的なプロジェクトです!FirebirdとPostgreSQLでは、事態はさらに複雑になります。一方では、1つのポイントのギャップは依然としてギャップであり、他方では、特にこのスコアが平均レベルの信頼性でV768について取得されているため、かなり小さい差です。自動生成コードに関するアラート...一般に、FirebirdとPostgreSQLに関して「i」をdotするためには、より徹底的な分析を行う必要がありますが、今のところ、それらを1か所に置いても誰も気分を害することはないと思います。いつか、これらの2つのプロジェクトの比較により慎重にアプローチできるようになるかもしれませんが、これはまったく異なる話です...結果はコードの品質によって評価されます。- 1位-FirebirdとPostgreSQL。
- 2位-MySQL。
そしてもう一度、これを含むあらゆるレビューと比較は主観的なものであり、いくつかの場合、異なるアプローチを使用すると結果が異なる可能性があることを思い出したいと思います(しかし、これはFirebirdとPostgreSQLの場合が多いですが、 MySQL)。静的解析はどうですか?さまざまな種類の欠陥を検出することの有用性を実証できたことを願っています。コードベースにそのようなものがあるかどうかを確認したいですか?それは時間だPVSの-メーカーを試してみてください!エラーなしでコードを書きますか?同僚のコードを確認してください;)この記事を英語圏の聴衆と共有したい場合は、翻訳へのリンクを使用してください:セルゲイヴァシリエフ。
Firebird、MySQL、PostgreSQLのコード品質の比較