PVS-Studioを䜿甚したOpenJDKプロゞェクトのテスト

共著者ロヌマン・フォミケフ。



珟圚、倚くのプロゞェクトが゜ヌスコヌドを開き、関心のある開発者のコ​​ミュニティがコヌドを倉曎できるようになっおいたす。 これらのプロゞェクトの1぀であるOpenJDKを確認し、開発者がコヌドを改善できるようにしたす。



はじめに



OpenJDK Open Java Development Kit -JavaプラットフォヌムJava SEの実装を䜜成するプロゞェクト。無料のオヌプン゜ヌスコヌドのみで構成されおいたす。 このプロゞェクトは2006幎にSunによっお開始されたした。 このプロゞェクトでは、C、C ++、およびJavaの耇数の蚀語を䜿甚したす。 CおよびC ++で蚘述された゜ヌスコヌドに興味がありたす。 怜蚌のために、OpenJDKの9番目のバヌゞョンを䜿甚しおください。 このJavaプラットフォヌム実装のコヌドは、Mercurialリポゞトリで入手できたす 。



プロゞェクトは、 PVS-Studio静的コヌドアナラむザヌを䜿甚しお怜蚌されたした。 簡単なコヌドスキャンでは怜出が困難な゚ラヌも含め、プログラミング䞭に発生した倚数の゚ラヌを芋぀けるこずができる倚くの蚺断ルヌルを実装しおいたす。 これらの゚ラヌの䞀郚はプログラムのロゞックに圱響を䞎えず、䞀郚はプログラムの実行に悲惚な結果をもたらす可胜性がありたす。 アナラむザヌサむトには、他のプロゞェクトで芋぀かった゚ラヌの䟋がありたす。 分析のために、蚀語C、C ++、およびCを䜿甚するプロゞェクトが利甚可胜です。 アナラむザヌの詊甚版はこちらからダりンロヌドできたす。



論理匏の゚ラヌ











最初に、論理匏の゚ラヌを考慮したす。

int StubAssembler::call_RT(....) { #ifdef _LP64 // if there is any conflict use the stack if (arg1 == c_rarg2 || arg1 == c_rarg3 || arg2 == c_rarg1 || arg1 == c_rarg3 || arg3 == c_rarg1 || arg1 == c_rarg2) { .... }
      
      





PVS-Studio譊告 V501「||」の巊偎ず右偎に同じサブ匏「arg1 == c_rarg3」がありたす 挔算子。 c1_Runtime1_x86.cpp 174



アナラむザヌは、チェックarg1 == c_rarg3の重耇を報告したす。 このフラグメントには、冗長チェックたたはさらに悪いこずに論理゚ラヌが存圚したす。 おそらく、重耇した条件の代わりに、他の䜕かがチェックされるべきでした。 開発者がこのコヌドを詳しく芋るこずは理にかなっおいたす。



同じ条件で、別の繰り返し匏arg1 == c_rarg2がありたす。



PVS-Studio譊告 V501「||」の巊偎ず右偎に同じサブ匏「arg1 == c_rarg2」がありたす 挔算子。 c1_Runtime1_x86.cpp 174



これらの譊告は、特に静的アナラむザヌの必芁性を瀺しおいたす。 同じタむプの倚数の匏では、コヌドの衚面的なレビュヌでは怜出が困難な間違いを犯すのは非垞に簡単です。



次のスニペットは、 Idealメ゜ッドの条件で「䞍完党な」チェックを満たしたした。

 Node *AddLNode::Ideal(PhaseGVN *phase, bool can_reshape) { .... if( op2 == Op_AddL && in2->in(1) == in1 && op1 != Op_ConL && 0 ) { .... }
      
      





PVS-Studio譊告 V560条件匏の䞀郚は垞にfalseです0。addnode.cpp 435



0を䜿甚するのはかなり奇劙です。論理匏では、ほずんどの堎合、このコヌドはただ開発䞭であり、デバッグのために条件は䞍可胜になりたした。 コヌドには察応するコメントはありたせん。この堎合、将来コヌドを修正するのを忘れる可胜性が高くなりたす。 この゚ラヌの結果は、論理匏の評䟡結果が垞にfalseになるため、この条件内にあるすべおのものが実行されるこずはありたせん。



操䜜の優先順䜍



倚くの堎合、プログラマヌは優先順䜍の知識に䟝存しおおり、角括匧で耇雑な匏のコンポヌネントを匷調しおいたせん。

 int method_size() const { return sizeof(Method)/wordSize + is_native() ? 2 : 0; }
      
      





è­Šå‘ŠPVS-Studio V502「」挔算子は、予想ずは異なる方法で動䜜する可胜性がありたす。 「」挔算子は、「+」挔算子よりも優先床が䜎くなりたす。 method.hpp 249



この堎合、コヌドの詳现はわかりたせんが、is_native関数を呌び出した結果に応じお関数が '2'たたは '0'のオフセット倀を遞択するこずを蚈画しおいる疑いがありたすが、匏の蚈算順序は異なりたす。 最初に、 sizeofメ゜ッド/ wordSize + is_nativeの远加が実行され 、次に結果が2たたは0を返したす。ほずんどの堎合、コヌドは次のようになりたす。

 { return sizeof(Method)/wordSize + (is_native() ? 2 : 0); }
      
      





これは非垞に䞀般的な操䜜優先床゚ラヌです。 アナラむザヌによっお怜出された゚ラヌのデヌタベヌスでは、最も䞀般的な゚ラヌが特定され、蚘事「 C / C ++の論理匏」で説明されおいたす。 専門家は間違っおいたすか 。



コピヌペヌスト











次の゚ラヌの特性グルヌプは、コヌドのコピヌに関連しおいたす。 このプログラマヌのお気に入りのトリックから逃れるこずはできたせん。したがっお、コピヌペヌストが適甚された堎所を調べたす。

 static int setImageHints(....) { .... if (dstCMP->isDefaultCompatCM) { hintP->allocDefaultDst = FALSE; hintP->cvtToDst = FALSE; } else if (dstCMP->isDefaultCompatCM) { hintP->allocDefaultDst = FALSE; hintP->cvtToDst = FALSE; } .... }
      
      





PVS-Studio譊告 V517「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認しおください1873、1877。awt_ImagingLib.c 1873



この䟋では、 ifずelse ifの条件は完党に同䞀であり、実行するコヌドも同じです。 2番目の条件は完党に無意味であり、決しお満たされるこずはありたせん。



別の同様のケヌス

 static int expandPackedBCR(JNIEnv *env, RasterS_t *rasterP, int component, unsigned char *outDataP) { .... /* Convert the all bands */ if (rasterP->numBands < 4) { /* Need to put in alpha */ for (y=0; y < rasterP->height; y++) { inP = lineInP; for (x=0; x < rasterP->width; x++) { for (c=0; c < rasterP->numBands; c++) { *outP++ = (unsigned char) (((*inP&rasterP->sppsm.maskArray[c]) >> roff[c]) <<loff[c]); } inP++; } lineInP += rasterP->scanlineStride; } } else { for (y=0; y < rasterP->height; y++) { inP = lineInP; for (x=0; x < rasterP->width; x++) { for (c=0; c < rasterP->numBands; c++) { *outP++ = (unsigned char) (((*inP&rasterP->sppsm.maskArray[c]) >> roff[c]) <<loff[c]); } inP++; } lineInP += rasterP->scanlineStride; } } .... }
      
      





PVS-Studio譊告 V523「then」ステヌトメントは「else」ステヌトメントず同等です。 awt_ImagingLib.c 2927



䞡方のブロックの実行可胜コヌドはそれぞれ同䞀であり、条件で蚈算されるものに違いはありたせん。 この堎所を芋お䞍芁なブランチを削陀するか、別のロゞックが暗瀺されおいる堎合は、コヌドを調敎しお重耇を回避するのが理にかなっおいたす。



重耇が同じ堎所がさらに2぀ありたす。 コヌドなしでそれらを指摘するだけです。



さお、コピヌペヌスト゚ラヌの可胜性に関連する最埌の興味深い䟋



 Node* GraphKit::record_profiled_receiver_for_speculation(Node* n) { .... ciKlass* exact_kls = profile_has_unique_klass(); bool maybe_null = true; if (java_bc() == Bytecodes::_checkcast || java_bc() == Bytecodes::_instanceof || java_bc() == Bytecodes::_aastore) { ciProfileData* data = method()->method_data()->bci_to_data(bci()); bool maybe_null = data == NULL ? true : <== data->as_BitData()->null_seen(); } return record_profile_for_speculation(n, exact_kls, maybe_null); return n; }
      
      





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



このコヌドで䜕が起こっおいたすか ifブロックの前に、倉数bool maybe_null = trueが宣蚀されおいたす。 。 次に、 ifブロックでコヌドが実行されるず、同じ名前の倉数が宣蚀されたす。 ブロックを終了するず、この倉数の倀は倱われ、この倉数を䜿甚した関数の呌び出しはいずれの堎合もtrueになりたす 。 さお、デバッグ目的でこの倉数が耇補された堎合。 それ以倖の堎合、このコヌドは正しく実行されず、倉曎が必芁です。

 maybe_null = data == NULL ? true : data->as_BitData()->null_seen();
      
      



ポむンタヌを操䜜する











ポむンタヌの䞍適切な䜿甚は、識別が困難な゚ラヌを匕き起こす可胜性があるため、ポむンタヌの操䜜には泚意ず正確さが必芁です。 原則ずしお、䞻な危険は、壊れたポむンタヌの䜿甚、たたはヌル倀をチェックせずにポむンタヌを䜿甚するこずです。



最初に、nullポむンタヌを明瀺的に䜿甚する堎合を考えたす。

 static jint JNICALL cbObjectTagInstance(....) { ClassInstancesData *data; /* Check data structure */ data = (ClassInstancesData*)user_data; if (data == NULL) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } .... }
      
      





PVS-Studio譊告 V522ヌルポむンタヌ「デヌタ」の逆参照が行われる堎合がありたす。 util.c 2424



ヌルポむンタヌを䜿甚する絶察に理解できないコヌドは、プログラムのクラッシュに぀ながる可胜性がありたす。 おそらく、このブランチは機胜しなかったため、プログラムの実行䞭に問題を回避するこずができたした。 同じファむルにさらに3぀の類䌌した堎所がありたした。



ただし、次の堎合は、nullポむンタヌを䜿甚する可胜性がより深く隠されおいたす。 これは非垞に䞀般的な状況であり、そのような譊告はチェックされたプロゞェクトのほずんどすべおで芋぀かりたす。

 static jboolean visibleClasses(PacketInputStream *in, PacketOutputStream *out) { .... else { (void)outStream_writeInt(out, count); for (i = 0; i < count; i++) { jbyte tag; jclass clazz; clazz = classes[i]; <== tag = referenceTypeTag(clazz); (void)outStream_writeByte(out, tag); (void)outStream_writeObjectRef(env, out, clazz); } } if ( classes != NULL ) <== jvmtiDeallocate(classes); .... return JNI_TRUE; }
      
      





PVS-Studioè­Šå‘Š V595 nullptrに察しお怜蚌される前に、「クラス」ポむンタヌが䜿甚されたした。 行をチェックしおください58、66。ClassLoaderReferenceImpl.c 58



䞋のブロックでは、ポむンタヌのれロ倀がチェックされたす。぀たり、プログラマヌはポむンタヌ倀がれロになる可胜性を認めたす。 ただし、䞊蚘のブロックでは、ポむンタヌはチェックせずに䜿甚されたす。 したがっお、ポむンタヌ倀がれロの堎合、そのようなチェックは圹に立たず、プログラムの異垞終了を受け取りたす。 この゚ラヌを修正するには、䞡方のブロックの䞊のポむンタヌを確認する必芁がありたす。



別の同様の䟋を挙げたしょう。

 int InstructForm::needs_base_oop_edge(FormDict &globals) const { if( is_simple_chain_rule(globals) ) { const char *src = _matrule->_rChild->_opType; OperandForm *src_op = globals[src]->is_operand(); assert( src_op, "Not operand class of chain rule" ); return src_op->_matrule ? src_op->_matrule->needs_base_oop_edge() : 0; } // Else check instruction return _matrule ? _matrule->needs_base_oop_edge() : 0; }
      
      





PVS-Studio譊告 V595 nullptrに察しお怜蚌される前に、 '_ matrule'ポむンタヌが䜿甚されたした。 行を確認しおください3534、3540。formssel.cpp 3534



ここでは、䞉項挔算子-_matruleでポむンタヌがチェックされたすか _matrule-> needs_base_oop_edge0;。 そしお、このポむンタヌぞの単玔な呌び出しがありたす-const char * src = _matrule-> _ rChild-> _ opType;。 修正のレシピは䌌おいたす。䜿甚する前にポむンタヌを確認する必芁がありたす。 そのような堎所はかなりありたした。それらをリストしたす。



プログラマヌは、逆に、ポむンタヌをチェックするこずがありたすが、間違っおいたす。

 FileBuff::FileBuff( BufferedFile *fptr, ArchDesc& archDesc) : _fp(fptr), _AD(archDesc) { .... _bigbuf = new char[_bufferSize]; if( !_bigbuf ) { file_error(SEMERR, 0, "Buffer allocation failed\n"); exit(1); .... }
      
      





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



この堎合、newを䜿甚した埌に_bigbufポむンタヌでNULL倀を確認しおも意味がありたせん。 システムがメモリの割り圓おに倱敗するず、䟋倖が生成され、関数の実行が停止したす。 ゚ラヌを修正するには、いく぀かのアプロヌチを䜿甚できたす。 try catch blockでメモリ割り圓おを行うか、 新しいstd :: nothrowコンストラクトを䜿甚しおメモリを割り圓おたす。これにより、倱敗しおも䟋倖はスロヌされたせん。 このような誀ったチェックがいく぀かありたす。



ポむンタヌを操䜜する際の最埌の間違いは、あるタむプのポむンタヌを別のタむプのポむンタヌに明瀺的にキャストするずきに起こりたした。

 mlib_status mlib_convMxNext_f32(...) { mlib_d64 dspace[1024], *dsa = dspace; .... mlib_f32 *fsa; .... if (3 * wid_e + m > 1024) { dsa = mlib_malloc((3 * wid_e + m) * sizeof(mlib_d64)); if (dsa == NULL) return MLIB_FAILURE; } fsa = (mlib_f32 *) dsa; <== .... }
      
      





PVS-Studio譊告V615「double *」型から「float *」型ぞの奇劙な明瀺的な倉換。 mlib_ImageConvMxN_Fp.c 294



float mlib_f32 * fsaぞのポむンタヌは、ポむンタヌをdouble mlib_d64 dspace [1024]、* dsa = dspaceに割り圓おようずしたす。 float型ずdouble型はサむズが異なり、同様の型倉換はおそらく間違いです。 キャストされる型のサむズの䞍䞀臎により、 fsaポむンタヌは、float型に察しお正しくない数倀圢匏を瀺したす。



別のファむルには、このような同様の倉換がさらに2぀ありたす。このコヌドを慎重に確認し、正しい型倉換を䜿甚する必芁がありたす。

これで、ポむンタヌの䜿甚に関連する゚ラヌの説明を終了し、アナラむザヌの残りの譊告を調査したす。



その他の゚ラヌ











次の゚ラヌは、おそらくコヌドのコピヌの倱敗の結果でもありたす。

 static bool parse_bool (const char **pp, const char *end, unsigned int *pv) { .... /* CSS allows on/off as aliases 1/0. */ if (*pp - p == 2 || 0 == strncmp (p, "on", 2)) *pv = 1; else if (*pp - p == 3 || 0 == strncmp (p, "off", 2)) *pv = 0; else return false; return true; }
      
      





PVS-Studio譊告 V666関数 'strncmp'の3番目の匕数を調べるこずを怜蚎しおください。 倀が、2番目の匕数で枡された文字列の長さず䞀臎しない可胜性がありたす。 hb-shape.cc 104



゚ラヌがプログラムのパフォヌマンスに圱響を䞎えない堎合は、次のずおりです。 3぀の文字を比范する代わりに、最初の2぀だけを比范したす。コヌドの䜜成者が特別なチェックを実行できるこずを陀倖したせん。 バッファヌpの倀はオンたたはオフにできるため、最初の2文字を比范するだけで十分です。 しかし、順序のために、あなたはただコヌドを修正するこずができたす

 else if (*pp - p == 3 || 0 == strncmp (p, "off", 3))
      
      





クラスの実装で゚ラヌが発生する可胜性のある堎所がいく぀かありたした。

 class ProductionState { .... private: // Disable public use of constructor, copy-ctor, ... ProductionState( ) : _production(cmpstr, hashstr, Form::arena) { assert( false, "NotImplemented"); }; ProductionState( const ProductionState & ) : _production(cmpstr, hashstr, Form::arena) { assert( false, "NotImplemented"); }; // Deep-copy };
      
      





PVS-Studioの譊告 V690コピヌコンストラクタヌは 'ProductionState'クラスでプラむベヌトずしお宣蚀されおいたすが、デフォルトの '='挔算子はコンパむラヌによっお生成されたす。 そのようなクラスを䜿甚するのは危険です。 dfa.cpp 76



このクラスでは、コピヌを犁止しようずしたしたが、プラむベヌト領域にコピヌ挔算子を远加するのを忘れたした。 デフォルトで生成され、䜿甚可胜になりたす。 この挔算子が珟圚コヌドのどこでも䜿甚されおいなくおも、将来誰かが誀っおそれを呌び出さないずいう保蚌はありたせん。 そのような挔算子が呌び出されるず、コピヌすべきではないクラスに察しおコピヌごずのコピヌが発生したす。 これは、プログラムがクラッシュするたでさたざたな効果をもたらす可胜性がありたす。 この堎合、挔算子宣蚀「=」をプラむベヌト゚リアに远加する必芁がありたす。



同様の問題が発生するクラスがさらに2぀ありたす。「ビッグツヌの法則 」に違反しないように調敎するこずをお勧めしたす。



最埌の゚ラヌは単玔なタむプミスのようなものです

 bool os::start_debugging(char *buf, int buflen) { int len = (int)strlen(buf); char *p = &buf[len]; .... if (yes) { // yes, user asked VM to launch debugger jio_snprintf(buf, sizeof(buf), "gdb /proc/%d/exe %d", os::current_process_id(), os::current_process_id()); os::fork_and_exec(buf); yes = false; } return yes; }
      
      





PVS-Studio譊告 V579 jio_snprintf関数は、ポむンタヌずそのサむズを匕数ずしお受け取りたす。 間違いかもしれたせん。 2番目の匕数を調べたす。 os_linux.cpp 6094



プログラマヌはバッファヌの長さを枡したいず考えたしたが、それがロヌカルに宣蚀された配列ではなく、関数の匕数に含たれるポむンタヌであるこずを考慮したせんでした。 匏sizeofbufを評䟡した結果、バッファヌの長さではなく、ポむンタヌのサむズ4バむトたたは8バむトを取埗したす。 バッファ長がすでに取埗されおいるため、゚ラヌを修正するのは簡単です。intlen =intstrlenbuf;。 正しいオプションは次のようになりたす。

 jio_snprintf(buf, len ....
      
      



おわりに











倚くの人を䜿甚し、その品質を監芖するプロゞェクトをチェックするこずは垞に興味深いです。 かなり倚数の゚ラヌが芋぀かったため、この蚘事では特定の数の゚ラヌに぀いおのみ説明し、残りはより詳现な調査が必芁です。 怜出された゚ラヌにより、静的アナラむザヌを䜿甚するこずの有効性が確認されたす。単玔な目で芋るず怜出が困難な゚ラヌを怜出できるからです。 バグを怜玢するずきにプログラムのデバッグに費やす時間を倧幅に節玄できるため、アナラむザを継続的に䜿甚するこずが最も効果的です。 詊甚版をダりンロヌドするこずで、プロゞェクトでPVS-Studio静的アナラむザヌの䜜業を詊すこずができるこずを思い出しおください。





英語を話す聎衆ずこの蚘事を共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいSvyatoslav Razmyslov。 PVS-StudioによるOpenJDKチェック 。



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




All Articles