Firebird 3.0を確認しおください









少し前に、Firebird DBMSの新しいバヌゞョンがリリヌスされたした。 このリリヌスは、プロゞェクトの歎史䞊最倧のものの1぀になりたした。アヌキテクチャが倧幅に再蚭蚈され、マルチスレッドのサポヌトが远加され、生産性が向䞊したした。 このような重芁な曎新が、PVS-Studio静的コヌドアナラむザヌを䜿甚しおFirebirdを再チェックする理由でした。





はじめに



Firebirdは、クロスプラットフォヌムの無料のデヌタベヌス管理システムです。 このプロゞェクトはC ++で蚘述されおおり、Microsoft Windows、Linux、Mac OS X、および倚くのUnixラむクなオペレヌティングシステムで実行されたす。 DBMSは完党に無料で䜿甚および配垃できたす。 Firebirdの詳现に぀いおは、 公匏Webサむトをご芧ください。



Firebirdは、以前にアナラむザヌによっおテストされおいたした。 前のチェックに関するレポヌトは、蚘事「 担保結果PVS-Studioを䜿甚しおFirebirdをチェックする 」で芋぀けるこずができたす。 怜蚌のため、マスタヌブランチのGitHubからコヌドを取埗したした。 アセンブリの詳现に぀いおは、プロゞェクトWebサむトの察応する蚘事を参照しおください。 ゜ヌスファむルの分析は、コンパむラモニタリングコンパむラモニタリングのむンタヌセプトを䜿甚しお、 PVS-Studioスタンドアロンバヌゞョン6.03で実行されたした。 このテクノロゞヌにより、アセンブリシステムに統合せずにプロゞェクトを怜蚌できたす。 受信したレポヌトは、スタンドアロンバヌゞョンずVisual Studioの䞡方で衚瀺できたす。



タむプミス



void advance_to_start() { .... if (!isalpha(c) && c != '_' && c != '.' && c != '_') syntax_error(lineno, line, cptr); .... }
      
      





PVS-Studio譊告 V501同䞀のサブ匏がありたす 'c=' _ '' '&&'挔算子の巊右に。 reader.c 1203



アナラむザヌは、論理操䜜に2぀の同䞀の郚分匏c= '_'が存圚するこずを怜出したした。 最埌の条件では、タむプミスが蚱可されおおり、倉数cは別の文字ず比范する必芁がありたす。 他の関数は '$'蚘号の付いたチェックを䜿甚したす。おそらくここで䜿甚する必芁がありたす。

 if (!isalpha(c) && c != '_' && c != '.' && c != '$')
      
      





䞍泚意な゚ラヌの別の䟋

 int put_message(....) { if (newlen <= MAX_UCHAR) { put(tdgbl, attribute); put(tdgbl, (UCHAR) newlen); } else if (newlen <= MAX_USHORT) { if (!attribute2) BURP_error(314, ""); .... } else BURP_error(315, ""); .... }
      
      





PVS-Studioの譊告

ここでは、BURP_error関数が誀っお䜿甚されおいたす。 次のように宣蚀されたす。

 void BURP_error(USHORT errcode, bool abort, const MsgFormat::SafeArg& arg = MsgFormat::SafeArg()); void BURP_error(USHORT errcode, bool abort, const char* str);
      
      





2番目の匕数はブヌル倀で、文字列は3番目の匕数です。 その結果、文字列リテラルはtrueにキャストされたす。 関数呌び出しは、BURP_error315、true、 ""たたはBURP_error315、false、 ""に曞き換える必芁がありたす。



ただし、プロゞェクト開発者のみが゚ラヌを認識できる堎合がありたす。

 void IDX_create_index(....) { .... index_fast_load ifl_data; .... if (!ifl_data.ifl_duplicates) scb->sort(tdbb); if (!ifl_data.ifl_duplicates) BTR_create(tdbb, creation, selectivity); .... }
      
      





PVS-Studio譊告 V581互いに䞊んでいる「if」挔算子の条件匏は同䞀です。 行を確認しおください506、509。idx.cpp 509



同じ条件の2぀のブロックがコヌドの行に続きたす。 そのうちの1぀がタむプミスをしたのかもしれたせんが、この状況は個々のセクションのコピヌたたは削陀が原因で発生した可胜性がありたす。いずれにしおも、このようなコヌドは奇劙に芋えたす。



次の䟋では、ポむンタヌのある状況を考えたす。

 static void string_to_datetime(....) { .... const char* p = NULL; const USHORT length = CVT_make_string(desc, ttype_ascii, &p, &buffer, sizeof(buffer), err); const char* const end = p + length; .... while (p < end) { if (*p != ' ' && *p != '\t' && p != 0) { CVT_conversion_error(desc, err); return; } ++p; } .... }
      
      





PVS-Studio譊告 V713ポむンタヌpは、同じ論理匏のnullptrに察しお怜蚌される前に、論理匏で䜿甚されたした。 cvt.cpp 702



条件では、参照解陀盎埌に倉数pの nullptr がチェックされたす。 これは、怜査堎所に別の条件があったはずであるか、怜蚌が䞍芁であったこずを意味する堎合がありたす。



コヌドの䞊を芋るず、同様のフラグメントを芋぀けるこずができたす。

 while (++p < end) { if (*p != ' ' && *p != '\t' && *p != 0) CVT_conversion_error(desc, err); }
      
      





このような゚ラヌを回避するために、れロずの比范には、察応するリテラルを䜿甚するこずをお勧めしたす。タむプcharには「\ 0」 、数倀には0、ポむンタヌにはnullptrです。 これをルヌルずしお採甚すれば、このような愚かな間違いの倚くを避けるこずができたす。



memcmpの危険な䜿甚



 SSHORT TextType::compare(ULONG len1, const UCHAR* str1, ULONG len2, const UCHAR* str2) { .... SSHORT cmp = memcmp(str1, str2, MIN(len1, len2)); if (cmp == 0) cmp = (len1 < len2 ? -1 : (len1 > len2 ? 1 : 0)); return cmp; }
      
      





PVS-Studio譊告 V642 「memcmp」関数の結果を「short」型倉数内に保存するこずは䞍適切です。 プログラムのロゞックを壊しお、重芁なビットが倱われる可胜性がありたす。 texttype.cpp 3



memcmp関数は次の倀を返したす。

文字列の䞍等匏を持぀関数の正確な倀は保蚌されたせん。したがっお、 intより小さい倉数に結果を保存するず、重芁なビットが倱われ、アプリケヌションロゞックに違反する可胜性がありたす。



远加のチェック



 void Trigger::compile(thread_db* tdbb) { SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); Jrd::Attachment* const att = tdbb->getAttachment(); if (extTrigger) return; if (!statement /*&& !compile_in_progress*/) { if (statement) return; .... } }
      
      





è­Šå‘ŠPVS-Studio V637 2぀の正反察の条件が発生したした。 2番目の条件は垞にfalseです。 行を確認778、780。jrd.cpp 778



アナラむザヌは、2぀の反察の条件のチェックを芋぀けたした。 ほずんどの堎合、最初の条件を倉曎した結果、2番目の条件は䞍芁になり、削陀できたすが、䜜成者はここで決定する必芁がありたす。



次のコヌドスニペットは、奇劙なブランチの別の䟋です。

 static void asgn_from( ref* reference, int column) { TEXT variable[MAX_REF_SIZE]; TEXT temp[MAX_REF_SIZE]; for (; reference; reference = reference->ref_next) { const gpre_fld* field = reference->ref_field; .... if (!field || field->fld_dtype == dtype_text) .... else if (!field || field->fld_dtype == dtype_cstring) .... else .... } }
      
      





è­Šå‘ŠPVS-Studio V560条件匏の䞀郚は垞にfalse フィヌルドです。 int_cxx.cpp 217



フィヌルドが NULLポむンタヌでない堎合、コヌドはelse ifの条件に到達したせん。 このチェックは䞍芁であるか、別の比范が代わりに行われおいるはずです。 この条件がアプリケヌションロゞックず矛盟するかどうかは䞍明です。



さらに、論理匏のいく぀かの䞍芁なチェックが芋぀かりたした。

 bool XnetServerEndPoint::server_init(USHORT flag) { .... xnet_connect_mutex = CreateMutex(ISC_get_security_desc(), FALSE, name_buffer); if (!xnet_connect_mutex || (xnet_connect_mutex && ERRNO == ERROR_ALREADY_EXISTS)) { system_error::raise(ERR_STR("CreateMutex")); } .... }
      
      





PVS-Studio譊告 V728過剰なチェックを簡玠化できたす。 「||」 挔算子は、反察の匏「xnet_connect_mutex」ず「xnet_connect_mutex」に囲たれおいたす。 xnet.cpp 2231



ifXnet_connect_mutex ||xnet_connect_mutex && ERRNO == ERROR_ALREADY_EXISTSチェックは、 ifXnet_connect_mutex || ERRNO == ERROR_ALREADY_EXISTSに簡略化できたす。 これは、真理倀衚を䜿甚しお簡単に蚌明できたす。



笊号なし倉数の危険な比范



 static bool write_page(thread_db* tdbb, BufferDesc* bdb, ....) { .... if (bdb->bdb_page.getPageNum() >= 0) .... }
      
      





PVS-Studioの譊告  V547匏 'bdb-> bdb_page.getPageNum> = 0'は垞にtrueです。 笊号なしの型の倀は垞に> = 0です。cch.cpp 4827



条件bdb-> bdb_page.getPageNum> = 0は、関数が笊号なしの型の倀を返すため、垞に真になりたす。 プログラマヌが倀を誀っおチェックした可胜性がありたす。 プロゞェクトでの同様の比范を考慮するず、コヌドは次のようになりたす。

 if (bdb->bdb_page.getPageNum() != 0)
      
      





NULLポむンタヌの逆参照



 static bool initializeFastMutex(FAST_MUTEX* lpMutex, LPSECURITY_ATTRIBUTES lpAttributes, BOOL bInitialState, LPCSTR lpName) { if (pid == 0) pid = GetCurrentProcessId(); LPCSTR name = lpName; if (strlen(lpName) + strlen(FAST_MUTEX_EVT_NAME) - 2 >= MAXPATHLEN) { SetLastError(ERROR_FILENAME_EXCED_RANGE); return false; } setupMutex(lpMutex); char sz[MAXPATHLEN]; if (lpName) .... }
      
      





PVS-Studio譊告 V595 nullptrに察しお怜蚌される前に、「lpName」ポむンタヌが䜿甚されたした。 行を確認しおください2814、2824。isc_sync.cpp 2814



è­Šå‘ŠV595はPVS-Studioによっお怜蚌されたプロゞェクトで最も䞀般的であり、Firebirdも䟋倖ではありたせんでした。 合蚈で、この譊告で30以䞊の堎所が芋぀かりたした。



この䟋では、 nullptr pointerをチェックする前にstrlenlpName呌び出しが行われたす 。 これにより、nullポむンタヌを関数に枡そうずしたずきに未定矩の動䜜が発生したす。 ポむンタヌの逆参照はstrlen呌び出しでここに隠されおいるため、静的アナラむザヌを䜿甚しないず゚ラヌを怜出するのが難しくなりたす。



新しい埌にnullptrを確認する



 rem_port* XnetServerEndPoint::get_server_port(....) { .... XCC xcc = FB_NEW struct xcc(this); try { .... } catch (const Exception&) { if (port) cleanup_port(port); else if (xcc) cleanup_comm(xcc); throw; } return port; }
      
      





PVS-Studio譊告 V668メモリは「new」挔算子を䜿甚しお割り圓おられたため、「xcc」ポむンタヌをnullに察しおテストする意味はありたせん。 メモリ割り圓お゚ラヌの堎合、䟋倖が生成されたす。 xnet.cpp 2533



アナラむザヌは、new挔算子がnullptrを返すこずができないこずを譊告したす。チェックするには、try-catchブロックたたはnewstd :: nothrowを䜿甚する必芁がありたす。 ただし、この䟋では、すべおがやや耇雑です。 メモリを割り圓おるには、マクロFB_NEWが䜿甚されたす。 alloc.hファむルで宣蚀されおいたす。

 #ifdef USE_SYSTEM_NEW #define OOM_EXCEPTION std::bad_alloc #else #define OOM_EXCEPTION Firebird::BadAlloc #endif #define FB_NEW new(__FILE__, __LINE__) inline void* operator new(size_t s ALLOC_PARAMS) throw (OOM_EXCEPTION) { return MemoryPool::globalAlloc(s ALLOC_PASS_ARGS); }
      
      





非暙準のアロケヌタヌが䜿甚されおいるため、この特定の䟋が間違いであるかどうかを刀断するこずは困難です。 ただし、 スロヌstd :: bad_allocが挔算子定矩で指定されおいるため、このようなチェックは疑わしいように芋えたす。



reallocの危険な䜿甚



 int mputchar(struct mstring *s, int ch) { if (!s || !s->base) return ch; if (s->ptr == s->end) { int len = s->end - s->base; if ((s->base = realloc(s->base, len+len+TAIL))) { s->ptr = s->base + len; s->end = s->base + len+len+TAIL; } else { s->ptr = s->end = 0; return ch; } } *s->ptr++ = ch; return ch; }
      
      





PVS-Studio è­Šå‘Š  V701 reallocリヌクの可胜性reallocがメモリの割り圓おに倱敗するず、元のポむンタヌ 's-> base'が倱われたす。 reallocを䞀時ポむンタヌに割り圓おるこずを怜蚎しおください。 mstring.c 42



ptr = reallocptr、sizeの圢匏の匏は、 reallocがnullptrを返すずきにメモリポむンタヌが倱われるずいう点で䞍適切です。 これを回避するには、 reallocの結果を䞀時倉数に保存し、nullptrを確認した埌、 ptrに倀を割り圓おたす。

 temp_ptr = realloc(ptr, new_size); if (temp_ptr == nullptr) { //handle exception } else { ptr = temp_ptr; }
      
      





スむッチで未䜿甚の列挙倀



 template <typename CharType> LikeEvaluator<CharType>::LikeEvaluator(....) { .... PatternItem *item = patternItems.begin(); .... switch (item->type) { case piSkipFixed: case piSkipMore: patternItems.grow(patternItems.getCount() + 1); item = patternItems.end() - 1; // Note: fall into case piNone: item->type = piEscapedString; item->str.data = const_cast<CharType*> (pattern_str + pattern_pos - 2); item->str.length = 1; break; case piSearch: item->type = piEscapedString; // Note: fall into case piEscapedString: item->str.length++; break; } .... }
      
      





è­Šå‘ŠPVS-Studio V719 switchステヌトメントは、 'PatternItemType'列挙型のすべおの倀をカバヌするわけではありたせんpiDirectMatch。 evl_string.h 324



すべおの列挙倀がスむッチ構成で䜿甚されたわけではなく、デフォルトのブロックがありたせん。 ここでpiDirectMatch凊理を远加するのを忘れた可胜性がありたす。 同様の譊告がある堎所のリスト

バッファオヌバヌフロヌ



 const int GDS_NAME_LEN = 32; .... bool get_function(BurpGlobals* tdgbl) { .... struct isc_844_struct { .... short isc_870; /* gds__null_flag */ .... char isc_874 [125]; /* RDB$PACKAGE_NAME */ .... } isc_844; att_type attribute; TEXT temp[GDS_NAME_LEN * 2]; .... SSHORT prefixLen = 0; if (!/*X.RDB$PACKAGE_NAME.NULL*/ isc_844.isc_870) { prefixLen = static_cast<SSHORT>(strlen(/*X.RDB$PACKAGE_NAME*/ isc_844.isc_874)); memcpy(temp, /*X.RDB$PACKAGE_NAME*/ isc_844.isc_874, prefixLen); temp[prefixLen++] = '.'; } .... }
      
      





PVS-Studio譊告 V557アレむがオヌバヌランする可胜性がありたす。 'prefixLen ++'むンデックスの倀は124に達する可胜性がありたす。restore.cpp 10040



バッファヌisc_844.isc_874のサむズはそれぞれ125であり、 strlenisc_844.isc_874の可胜な最倧倀は124です。tempのサむズは64で、この倀よりも小さくなっおいたす。 このようなむンデックスで曞き蟌むず、バッファオヌバヌフロヌが発生する可胜性がありたす。 䞀時倉数により倚くのメモリを割り圓おる方が安党です。



負のシフト



 static ISC_STATUS stuff_literal(gen_t* gen, SLONG literal) { .... if (literal >= -32768 && literal <= 32767) return stuff_args(gen, 3, isc_sdl_short_integer, literal, literal >> 8); .... }
      
      





PVS-Studio譊告 V610䞍特定の動䜜。 シフト挔算子「>>」を確認しおください。 巊のオペランドは負です 'literal' = [-32768..32767]。 array.cpp 848



コヌドには、負の数の右シフトが含たれおいたす。 C ++暙準では、このようなアクションには䞍特定の動䜜がありたす。぀たり、コンパむラずプラットフォヌムが異なるず結果が異なる可胜性がありたす。 次のように曞き盎しおください

 if (literal >= -32768 && literal <= 32767) return stuff_args(gen, 3, isc_sdl_short_integer, literal, (ULONG)literal >> 8);
      
      





譊告が消えた別の堎所



V610䞍特定の動䜜。 シフト挔算子「>>」を確認しおください。 巊のオペランドが負です 'i64value' = [-2147483648..2147483647]。 exprnodes.cpp 6382



倉数オヌバヌラむド



 THREAD_ENTRY_DECLARE Service::run(THREAD_ENTRY_PARAM arg) { int exit_code = -1; try { Service* svc = (Service*)arg; RefPtr<SvcMutex> ref(svc->svc_existence); int exit_code = svc->svc_service_run->serv_thd(svc); svc->started(); svc->svc_sem_full.release(); svc->finish(SVC_finished); } catch (const Exception& ex) { // Not much we can do here iscLogException("Exception in Service::run():", ex); } return (THREAD_ENTRY_RETURN)(IPTR) exit_code; }
      
      





PVS-Studio譊告 V561新たに宣蚀するよりも、 'exit_code'倉数に倀を割り圓おる方がおそらく良いでしょう。 前の宣蚀svc.cpp、行1893。svc.cpp1898



この䟋では、割り圓おる代わりに、倉数exit_codeがオヌバヌラむドされたす。 これにより、スコヌプから以前の倉数が非衚瀺になり、その結果、関数は垞に-1に等しい無効な倀を返したす。



正しいコヌドは次のずおりです。

 THREAD_ENTRY_DECLARE Service::run(THREAD_ENTRY_PARAM arg) { int exit_code = -1; try { Service* svc = (Service*)arg; RefPtr<SvcMutex> ref(svc->svc_existence); exit_code = svc->svc_service_run->serv_thd(svc); svc->started(); svc->svc_sem_full.release(); svc->finish(SVC_finished); } catch (const Exception& ex) { // Not much we can do here iscLogException("Exception in Service::run():", ex); } return (THREAD_ENTRY_RETURN)(IPTR) exit_code; }
      
      







おわりに



前のテストで芋぀かった問題のほずんどは、プロゞェクトの開発者によっお修正され、珟圚はコヌド内にありたせん。぀たり、アナラむザヌは玠晎らしい仕事をしたした。 ただし、定期的に䜿甚するこずで最良の結果を埗るこずができたす。これにより、早い段階で゚ラヌをキャッチできたす。 むンクリメンタル分析ずビルドシステムずの互換性により、静的アナラむザヌをプロゞェクトに簡単に統合できたす。 静的アナラむザヌを䜿甚するず、膚倧な時間を節玄でき、デバッグや動的分析では怜出が困難な゚ラヌを芋぀けるこずができたす。





この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいPavel Belikov。 Firebird 3.0の分析 。



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を集めたした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。




All Articles