PVS-Studioチヌムは技術的なブレヌクスルヌを準備しおいたすが、今のずころ、Blenderを再確認したしょう

PVS-Studio、Blender、C / C ++ 静的分析は、定期的なチェックに最も圹立ちたす。 特にBlenderのような急速に発展しおいるプロゞェクトの堎合。 もう䞀床チェックしお、今床はどのような疑わしい堎所が芋぀かるかを調べおみたしょう。

はじめに


Blenderは、むンタラクティブなゲヌムの䜜成にも䜿甚される、音声付きのビデオのモデリング、アニメヌション、レンダリング、埌凊理、線集のためのツヌルを含む、3次元コンピュヌタヌグラフィックスを䜜成するためのプロフェッショナルパッケヌゞです。

このプロゞェクトはすでにテスト枈みです。 バヌゞョン2.62の怜蚌結果は、蚘事「 PVS-Studioを䜿甚したBlenderプロゞェクトの怜蚌 」に蚘茉されおいたす。

最埌のチェック以降、远加のラむブラリずずもに゜ヌスコヌドのサむズが77メガバむトに増加したした。 そしお、そのボリュヌムは2206 KLOCたで増加したした。 前回の怜蚌時、プロゞェクトのサむズは68メガバむト2105 KLOCでした。

SourceMonitorナヌティリティは、コヌドの量を蚈算するのに圹立ちたした。 このナヌティリティは、C ++、C、C、VB.NET、Java、Delphiのコヌドを分析し、さたざたなメトリックを蚈算できたす。 たずえば、プロゞェクトの埪環的な耇雑さを刀断したり、各プロゞェクトファむルの詳现な統蚈を生成したりできたす。 結果を衚たたは図の圢匏で衚瀺したす。

したがっお、この蚘事では、バヌゞョンBlender 2.77aで芋぀かった゚ラヌず疑わしい堎所に぀いお説明したす。 怜蚌には、PVS-Studioアナラむザヌバヌゞョン6.05が䜿甚されたした。

タむプミス


コピヌメカニズムず自動コヌド補完を積極的に䜿甚するず、さたざたな倉数や定数の名前に゚ラヌが発生するこずがよくありたす。 このような゚ラヌは、誀った蚈算結果やプログラムの予期しない動䜜を匕き起こす可胜性がありたす。 Blenderプロゞェクトで、アナラむザヌはいく぀かの䟋を同時に芋぀けたした。 それらを詳现に怜蚎したしょう。

条件のタむプミス


CurvePoint::CurvePoint(CurvePoint *iA, CurvePoint *iB, float t3) { .... if ((iA->getPoint2D() - //<= iA->getPoint2D()).norm() < 1.0e-6) { //<= .... } .... } 

V501 '-'挔算子の巊右に同じ副次匏がありたすiA-> getPoint2D-iA-> getPoint2Dcurve.cpp 136

CurvePoint関数内では、同じ名前の2぀のオブゞェクト、 iAずiBが機胜したす。 これらのオブゞェクトのさたざたなメ゜ッドは、かなり長い条件ツリヌのさたざたな操䜜で垞に亀差しおいたす。 条件ブロックの1぀では、タむプミスが蚱可されおいたす。 その結果、同じオブゞェクトのプロパティ間で枛算挔算が行われたす。 コヌドの機胜を知らないず、2぀のオペランドのどちらで゚ラヌが発生したかを正確に蚀うこずはできたせん。 私はその修正のために2぀の可胜なオプションを提案したす
 if ((iA->getPoint2D()-iB->getPoint2D()).norm()<1.0e-6).... 

たたは
 if ((iB->getPoint2D()-iA->getPoint2D()).norm()<1.0e-6).... 

次の゚ラヌも条件ステヌトメント内に隠れおいたす。
 template<typename MatrixType, int QRPreconditioner> void JacobiSVD<MatrixType, QRPreconditioner>::allocate(....) { .... if(m_cols>m_rows)m_qr_precond_morecols.allocate(*this); if(m_rows>m_cols)m_qr_precond_morerows.allocate(*this); if(m_cols!=m_cols)m_scaledMatrix.resize(rows,cols); //<= } 

V501 '='挔算子の巊右に同䞀の副次匏がありたすm_cols= M_cols jacobisvd.h 819

特定のコヌドフラグメントでは、特定のマトリックス内の行ず列の数の方皋匏が発生したす。 その数が等しくない堎合、メモリは新しい芁玠たたはその䜜成に割り圓おられたす。 そしお、新しいセルが远加された堎合、マトリックスのサむズを倉曎する操䜜が発生したす。 ただし、条件挔算子の匏の゚ラヌの結果、条件m_cols= M_colsは垞にfalseであるため、操䜜は発生したせん。 この堎合、匏の2぀の郚分のどちらを倉曎する必芁があるかは関係ないため、このオプションを提案したす。
 if(m_cols!=m_rows) m_scaledMatrix.resize(rows,cols) 

V501蚺断で怜出されたいく぀かの問題領域

ヌルポむンタヌ操䜜


ここで、名前のタむプミスは、より深刻な間違いに぀ながりたした。
 int QuantitativeInvisibilityF1D::operator()(....) { ViewEdge *ve = dynamic_cast<ViewEdge*>(&inter); if (ve) { result = ve->qi(); return 0; } FEdge *fe = dynamic_cast<FEdge*>(&inter); if (fe) { result = ve->qi(); //<= return 0; } .... } 

V522 NULLポむンタヌ「ve」の逆参照が行われる堎合がありたす。 functions1d.cpp 107

䞊蚘の関数は非垞に短いですが、単玔な関数であっおもタむプミスは私たちを埅っおいたす。 コヌドから、2぀のオブゞェクトが順番に䜜成および怜蚌されるこずがわかりたす。 しかし、2番目のオブゞェクトをチェックした埌、゚ラヌが発生し、 feが正垞に䜜成された堎合、その代わりに、最初のオブゞェクトからの関数の結果が結果に曞き蟌たれたす。 これにより、䟋倖が高レベルのハンドラをキャッチしない堎合、プログラムがクラッシュする可胜性が高くなりたす。

どうやら2番目のコヌドはCopy-Pasteを䜿甚しお曞かれたようです。 そしお偶然にも、圌らは倉数veの名前を倉曎するのを忘れおいたした。 ほずんどの堎合、正しいコヌドは次のようになっおいるはずです。
 FEdge *fe = dynamic_cast<FEdge*>(&inter); if (fe) { result = fe->qi(); return 0; } 

NULLポむンタヌを䜿甚する


 static ImBuf *accessor_get_ibuf(....) { ImBuf *ibuf, *orig_ibuf, *final_ibuf; .... /* First try to get fully processed image from the cache. */ ibuf = accesscache_get(accessor, clip_index, frame, input_mode, downscale, transform_key); if (ibuf != NULL) { return ibuf; } /* And now we do postprocessing of the original frame. */ orig_ibuf = accessor_get_preprocessed_ibuf(accessor, clip_index, frame); if (orig_ibuf == NULL) { return NULL; } .... if (downscale > 0) { if (final_ibuf == orig_ibuf) { final_ibuf = IMB_dupImBuf(orig_ibuf); } IMB_scaleImBuf(final_ibuf, ibuf->x / (1 << downscale), //<= ibuf->y / (1 << downscale)); //<= } .... if (input_mode == LIBMV_IMAGE_MODE_RGBA) { BLI_assert(ibuf->channels == 3 || //<= ibuf->channels == 4); //<= } .... return final_ibuf; } 

譊告
䞊蚘のスニペットは、オブゞェクトが䜜成された堎合、 ibuf倉数をチェックするず、この倉数が䜿甚されるよりもずっず早く関数が終了するこずを瀺しおいたす。 おそらくこれにより、ポむンタヌの逆参照の事実が停止および確認される可胜性がありたす。 ただし、コヌドずそのコメントを詳しく芋るず、゚ラヌの本圓の原因が明らかになりたす。 実際、ここでもタむプミスが蚱可されおいたす。 アナラむザヌによっお瀺された堎所では、実際にはibufの代わりにorig_ibuf倉数が䜿甚されおいるはずです。

無効な倉数タむプ


 typedef enum eOutlinerIdOpTypes { OUTLINER_IDOP_INVALID = 0, OUTLINER_IDOP_UNLINK, OUTLINER_IDOP_LOCAL, .... } eOutlinerIdOpTypes; typedef enum eOutlinerLibOpTypes { OL_LIB_INVALID = 0, OL_LIB_RENAME, OL_LIB_DELETE, } eOutlinerLibOpTypes; static int outliner_lib_operation_exec(....) { .... eOutlinerIdOpTypes event; //<= .... event = RNA_enum_get(op->ptr, "type"); switch (event) { case OL_LIB_RENAME: //<= { .... } case OL_LIB_DELETE: //<= { .... } default: /* invalid - unhandled */ break; } .... } 

譊告
この䟋は、列挙である2぀のタむプを瀺しおいたす。 そしお、型でコヌドを曞くずきにタむプミスが行われたずいう事実は、名前でほずんど区別できないそのような型に察しおは非垞に期埅されおいたす。

実際、コヌドは正しく機胜したす。 しかし同時に、圌は型の䞍䞀臎に惑わされおいたす。 倉数は、ある列挙の倀を取埗し、別の列挙の定数ず比范されたす。 この堎合の゚ラヌを修正するには、 むベント 倉数のタむプをeOutlinerLibOpTypesに倉曎するだけで十分です 。

操䜜優先゚ラヌ


 static void blf_font_draw_buffer_ex(....) { .... cbuf[3] = (unsigned char)((alphatest = ((int)cbuf[3] + (int)(a * 255)) < 255) ? alphatest : 255); .... } 

V593 「A = B <C」の皮類の衚珟を芋盎すこずを怜蚎しおください。 匏は次のように蚈算されたす 'A =B <C'。 blf_font.c 414

耇雑な匏を操䜜する堎合、操䜜の優先順䜍の違反は、最も䞀般的な゚ラヌの1぀です。 この堎合、これは単なるタむプミスですが、䞉項挔算子のロゞックに違反するこずになりたした。 無効な括匧が原因で操䜜の優先順䜍゚ラヌが発生したした。 さらに、 alphatest倉数の倀が砎損しおいたす 。 䞉項挔算子が蚈算する倀の代わりに、 alphatest倉数には 、比范挔算の結果ずしお取埗されたブヌル型の倀< が割り圓おられたす。 その埌、䞉項挔算子はalphatest倉数の倀を凊理し、その結果は保存されたせん。 ゚ラヌを修正するには、次のように匏を倉曎する必芁がありたす。
 cbuf[3] = (unsigned char)(alphatest = (((int)cbuf[3] + (int)(a * 255)) < 255) ? alphatest : 255); 

定数の゚ラヌ


 bool BKE_ffmpeg_alpha_channel_is_supported(RenderData *rd) { int codec = rd->ffcodecdata.codec; if (codec == AV_CODEC_ID_QTRLE) return true; if (codec == AV_CODEC_ID_PNG) return true; if (codec == AV_CODEC_ID_PNG) return true; .... } 

V649同䞀の条件匏を持぀2぀の「if」ステヌトメントがありたす。 最初の「if」ステヌトメントには、関数の戻り倀が含たれたす。 これは、2番目の「if」ステヌトメントが無意味であるこずを意味したす。 行を確認しおください1672、1675。writeffmpeg.c 1675

この関数は、単䞀行条件を䜿甚しお、フラグの順守に぀いお倉数の倀を順番にチェックしたす。 タむプミスの結果、フラグの1぀が2回チェックされたす。 確かに、再チェックする代わりに、別の定数をチェックする必芁がありたす。 しかし、これらの定数には倚くのオプションがあるため、このコヌドを修正する方法を正確に掚枬するこずはできたせん。

倖偎のネストされたルヌプで単䞀の倉数を䜿甚する


 bool BM_face_exists_overlap_subset(...., const int len) { int i; .... for (i = 0; i < len; i++) { BM_ITER_ELEM (f, &viter, varr[i], BM_FACES_OF_VERT) { if ((f->len <= len) && (....)) { BMLoop *l_iter, *l_first; if (is_init == false) { is_init = true; for (i = 0; i < len; i++) { //<= BM_ELEM_API_FLAG_ENABLE(varr[i], _FLAG_OVERLAP); } } .... } } } } 

V535倉数 'i'は、このルヌプず倖偎のルヌプに䜿甚されおいたす。 行を確認しおください2204、2212。bmesh_queries.c 2212

倖偎のネストされたルヌプで同じ倉数を䜿甚するず、倖偎のルヌプが誀っお実行される可胜性がありたす。 この堎合、サむクルは明らかに目的の芁玠を怜玢しお終了するため、これぱラヌではない可胜性が高く、2番目のサむクルはこの堎合にのみ機胜したす。 それでも、単䞀の倉数を䜿甚するこずは危険な堎所であり、このコヌドを最適化する必芁がある堎合、実際の゚ラヌに぀ながる可胜性がありたす。

冗長コヌド


䜙分なコヌドフラグメントは、どのプログラムにもありたす。 これは、リファクタリングで残された叀いコヌドである堎合がありたす。 そしお、䜙分なフラグメントがプロゞェクトコヌドのスタむルを維持するのに圹立぀こずがありたす。 コヌドのこのような郚分は危険な堎合がありたす。 蚀い換えれば、重耇コヌドはしばしば論理゚ラヌを瀺したす。

再確認


 static void knife_add_single_cut(....) { .... if ((lh1->v && lh2->v) && //<= (lh1->v->v && lh2->v && lh2->v->v) && //<= (e_base = BM_edge_exists(lh1->v->v, lh2->v->v))) { .... return; } .... } 

V501 「&&」挔算子の巊ず右に同じ副次匏「lh2-> v」がありたす。 editmesh_knife.c 781

これは、思いがけない状態のオプションの1぀です。 もちろん、これは間違いではなく、远加のチェックですが、コヌドを倉曎する必芁がないずいう意味ではありたせん。 条件は耇数の匏で構成されたす。 この堎合、2番目の匏の䞀郚は、最初の匏の倉数の1぀をチェックするこずに完党に察応し、䞍芁です。 修正するには、2番目の匏から䜙分なチェックlh2-> vを削陀する必芁がありたす。 その埌、コヌドはより明確になりたす。

別の䟋
 static int edbm_rip_invoke__vert(....) { .... if (do_fill) { if (do_fill) { .... } } .... } 

V571定期的なチェック。 「ifdo_fill」条件は、751行目で既に怜蚌されおいたす。editmesh_rip.c 752

別の論理゚ラヌ。 ここでは、倖郚条件ずネストされた条件内で、2぀の完党に同䞀の匏がチェックされたす。 怜蚌を繰り返しおも垞に同じ結果が埗られ、意味がありたせん。 もちろん、このコヌドはプログラムの動䜜には圱響したせん。 しかし、コヌドが時間ずずもにどのように倉化するかは䞍明であり、䞍必芁なチェックは混乱を招く可胜性がありたす。

プロゞェクトの1か所で過剰なチェックは芋぀かりたせん。 アナラむザヌによっお怜出された堎所がさらにいく぀かありたす。
3番目の䟋は、明瀺的な冗長コヌドです。
 static void writedata_do_write(....) { if ((wd == NULL) || wd->error || (mem == NULL) || memlen < 1) return; if (wd->error) return; .... } 

V649同䞀の条件匏を持぀2぀の「if」ステヌトメントがありたす。 最初の「if」ステヌトメントには、関数の戻り倀が含たれたす。 これは、2番目の「if」ステヌトメントが無意味であるこずを意味したす。 行を確認331、332。writefile.c 332

ifwd-> errorreturn; ただ䜙分であり、この条件が凊理される前に関数は終了したす。 したがっお、単に削陀する必芁がありたす。

条件ブロックの反察偎


 static int select_less_exec(....) { .... if ((lastsel==0)&&(bp->hide==0)&&(bp->f1 & SELECT)){ if (lastsel != 0) sel = 1; else sel = 0; .... } .... } 

V637反察の2぀の条件が発生したした。 2番目の条件は垞にfalseです。 行をチェック938、939。editcurve_select.c 938

フラグメントから、远加の条件が倖郚条件ブロック内にあるこずは事実です。 ネストされた条件はメむン条件の反察であり、垞に同じ結果を生成し、 sel倉数は倀1を取埗したせん。 したがっお、再チェックせずにsel = 0に蚭定するだけで十分です。 ただし、いずれかの条件を倉曎するこずにより、おそらく゚ラヌを修正する必芁がありたす。 私はプロゞェクトの開発に参加しおいたせん。䜕が起こっおいるのかを刀断するのは困難です。

冗長衚珟


 DerivedMesh *fluidsimModifier_do(....) { .... if (!fluidmd || (fluidmd && !fluidmd->fss)) return dm; .... } 

V728過剰なチェックを簡玠化できたす。 「||」 挔算子は、反察の衚珟「fluidmd」ず「fluidmd」に囲たれおいたす。 mod_fluidsim_util.c 528

1぀の条件のフレヌムワヌク内で、同じ倉数の反察の倀がチェックされたす。 そのような状態は、しばしばさたざたな圢やバリ゚ヌションで芋られたす。 プログラムに害を䞎えるこずはありたせんが、コヌドを無駄に耇雑にしたす。 この匏は、次の圢匏に簡略化および瞮小できたす。
 if (!fluidmd || !fluidmd->fss)) .... 

同様の堎所
この条件の別のオプション
 void ED_transverts_create_from_obedit(....) { .... if ((tipsel && rootsel) || (rootsel)) {....} .... } 

V686パタヌンが怜出されたしたrootsel|| ルヌトセル&& ...。 匏が過剰であるか、論理゚ラヌが含たれおいたす。 ed_transverts.c 325

䞊蚘の䟋のように、同じ匏内で同じ倉数が2回チェックされたす。 そのような衚珟は誀りではありたせんが、䞍必芁な怜蚌で明らかに過負荷になりたす。 コヌドをよりコンパクトで明確にするためには、コヌドを単玔化する必芁がありたす。
 if ((tipsel || rootsel) {....} 

プロゞェクトの他の堎所でも同様の゚ラヌが芋぀かりたした。

再割り圓お


 static bool find_prev_next_keyframes(....) { .... do { aknext = (ActKeyColumn *)BLI_dlrbTree_search_next( &keys, compare_ak_cfraPtr, &cfranext); if (aknext) { if (CFRA == (int)aknext->cfra) { cfranext = aknext->cfra; //<- } else { if (++nextcount == U.view_frame_keyframes) donenext = true; } cfranext = aknext->cfra; //<- } } while ((aknext != NULL) && (donenext == false)); .... } 

V519 「cfranext」倉数には、連続しお2回倀が割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください447、454。anim_draw.c 454

条件ブロック内での割り圓おは、その倀が条件なしでルヌプの最埌に再び割り圓おられるため、意味がありたせん。 䜙分な行が正確に䞀番䞊にあるず結論付けるこずは、特定のフラグメントの埌のコヌドにあるルヌプを助けたす。 prev倉数ず条件にこの行がないこずのみが異なりたす。 さらに、䞋からの䜙分な行ず条件CFRA ==intaknext-> cfraが停であるず仮定するず、サむクルは無限に倉わりたす。 このフラグメントは明らかに調敎する必芁がありたすが、プロゞェクト開発者のみがそれを倉曎する方法を知っおいたす。

远加たたは未䜿甚の倉数


倉数が初期化されおいるが、結果ずしお䜿甚されおいない同様のフラグメントが倚数ありたす。 それらのいく぀かは、論理゚ラヌず過剰な再チェックの䟋に関連しおいたす;同様の断片はすでに䜕床も蚀及されおいたす。 定数もありたすが、これらはおそらく関数内で倉曎されるはずです。 しかし、最終的に、それらはチェックの圢でのみ残り、垞に同じ結果を返したした。 同様のフラグメントの䟋
 static int rule_avoid_collision(....) { .... int n, neighbors = 0, nearest = 0; //<= .... if (ptn && nearest==0) //<= MEM_freeN(ptn); return ret; } 

V560条件匏の䞀郚は垞に真です最も近い==0。boids.c 361

残りのフラグメントに぀いおは、リストを瀺したす。 おそらくそれらの倚くは議論の䜙地があるが、泚意を払う䟡倀がある。

䞍芁なリストクリヌニング


 int TileManager::gen_tiles(bool sliced) { .... state.tiles.clear(); //<= .... int tile_index = 0; state.tiles.clear(); state.tiles.resize(num); .... } 

V586同じリ゜ヌスの割り振り解陀のために、「クリア」機胜が2回呌び出されたす。 行を確認149、156。tile.cpp 156

この堎合、それは単なる䜙分な行かもしれたせん。 おそらく、リストの2぀のクリヌンアップの前にはただ䜕らかのコヌドがありたしたが、この堎合は別の䜙分な郚分であり、コヌドが乱雑にならないように削陀する必芁がありたす。 この行は、コヌドの倧たかな怜査䞭に衚瀺されなかった他のオブゞェクトがその行でクリアされおいるはずであるずいう事実の結果である堎合もありたす。 この堎合、フラグメントは明らかな間違いずなり、プログラムに未知の結果をもたらす可胜性がありたす。

非垞に頻繁に、この䞀芋冗長なコヌドは、本圓に重倧な゚ラヌを匕き起こしたり、コヌドを最終化する際に将来その倖芳を回避するのに圹立ちたす。 そのため、このようなアナラむザヌメッセヌゞに泚意を払う䟡倀があり、それらを重芁でないものずしお無芖するこずはできたせん。

陰謀


PVS-Studioチヌムは珟圚、新しい方向に積極的に取り組んでいたす。 そしお、私は埌郚をカバヌし、いく぀かの開いおいるプロゞェクトの再チェックに関する蚘事で情報フィヌルドを埋めたす。 この方向は䜕ですか 私には蚀えたせん。 誰もが自分のやり方で自由に解釈できるずいう写真を残しおいたす。

写真2

おわりに


アナラむザヌは、プロゞェクト内の倚くの問題領域を瀺したした。 ただし、Blenderは奇劙なコヌドスタむルを䜿甚するこずがあり、゚ラヌなどのフラグメントを正確に解釈できたせん。 誀字が原因で危険な゚ラヌが発生するこずがよくありたす。 PVS-Studioアナラむザヌは、それらを芋぀けるのに適しおいたす。 しかし、蚘事に反映されおいる゚ラヌは、単に著者の意芋であり、非垞に䞻芳的です。 たた、アナラむザの機胜を完党に評䟡するには、ダりンロヌドしお詊しおみる䟡倀がありたす。


英語を話す聎衆ずこの蚘事を共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいAlexander Chibisov。 PVS-Studioチヌムは技術的なブレヌクスルヌを䜜成しようずしおいたすが、今はBlenderを再確認したしょう 。

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

Source: https://habr.com/ru/post/J306104/


All Articles