LinuxバヌゞョンのPVS-Studioがディズニヌのツアヌに参加したす















最近、PVS-StudioアナラむザヌのLinuxバヌゞョンがリリヌスされたした。 その助けを借りお、倚くのオヌプン゜ヌスプロゞェクトがテストされたした。 それらの䞭には、Chromium、GCC、LLVMClangなどがありたす。 そしお今日、仮想珟実コミュニティのためにりォルトディズニヌアニメヌションスタゞオが開発したプロゞェクトがこのリストに加わりたす。 怜出されたアナラむザヌの譊告の確認を始めたしょう。



ディズニヌに぀いお少し



りォルトディズニヌは長幎にわたり、䞖界䞭のテレビ芖聎者を魅惑的なストヌリヌやキャラクタヌで喜ばせ、忘れられない䜓隓を提䟛しおきたした。 幎々、ディズニヌはたすたす刺激的で面癜く、実装が難しい映画や挫画を生み出しおいたす。 したがっお、芖芚効果に関するアヌティストの創造的なアむデアの実珟に貢献するさたざたなプログラムの開発に察するニヌズが高たっおいたす。



Walt Disney Animation Studiosのプログラマヌは、バヌチャルリアリティ業界のすべおのナヌザヌ向けにオヌプン゜ヌスCおよびC ++プログラムずしお利甚可胜な技術を䜜成するこずにより、アニメヌションおよび芖芚効果の専門家をサポヌトしおいたす。 これらのプログラムは次のずおりです。





ディズニヌのオヌプン゜ヌス゜フトりェアはhttps://disney.github.io/からダりンロヌドできたす 。



怜蚌結果



Walt Disneyが怜蚎しおいるプロゞェクトは小芏暡で、CおよびC ++で数䞇行のコヌドしかない。 したがっお、このような少数のプロゞェクト゚ラヌ。



パルティオプロゞェクト



















PVS-Studio譊告 V547匏 '"R"'は垞に真です。 PDA.cpp 90

ParticlesDataMutable* readPDA(....) { .... while(input->good()) { *input>>word; .... if(word=="V"){ attrs.push_back(simple->addAttribute(....); }else if("R"){ // <= attrs.push_back(simple->addAttribute(....); }else if("I"){ // <= attrs.push_back(simple->addAttribute(....); } index++; } .... }
      
      





アナラむザヌは、条件が垞に真であるずいうメッセヌゞを発行したした。 これは、 elseブランチで定矩されおいるアクションが実行されないずいう事実に぀ながりたす。 プログラマヌの䞍泚意のためにこの状況が発生したず思いたす。そのような゚ラヌに぀ながらない条件は次のようになりたす。

 .... if(word=="V"){ attrs.push_back(simple->addAttribute(....); }else if(word=="R"){ // <= attrs.push_back(simple->addAttribute(....); }else if(word=="I"){ // <= attrs.push_back(simple->addAttribute(....); } ....
      
      





PVS-Studio è­Šå‘Š  V528 「char」型ぞのポむンタヌが「\ 0」倀ず比范されるのは奇劙です。 おそらく以䞋を意味したす* charArray [i]= '\ 0'。 MC.cpp 109



 int CharArrayLen(char** charArray) { int i = 0; if(charArray != false) { while(charArray[i] != '\0') // <= { i++; } } return i; }
      
      





正しく理解できれば、 CharArrayLen関数はcharArray文字列の文字数をカりントしたす。 しかし、これは本圓にそうですか 私の意芋では、 char型ぞのポむンタヌが倀'\ 0'ず比范されるため、 whileルヌプの状態に゚ラヌがありたす。 ポむンタヌの逆参照操䜜が忘れられる可胜性が高いです。 したがっお、 whileルヌプの条件は次のようになりたす。



 while ((*charArray)[i] != '\0')
      
      





ちなみに、少し高い䜍眮にあるチェックも非垞に奇劙です。



 if(charArray != false)
      
      





怜蚌はもちろん機胜したすが、これを次のように眮き換える方がはるかに優れおいたす。



 if(charArray != nullptr)
      
      





䞀般的に、研修生が機胜を開発しおいたか、完了しおいないようです。 strlen関数を䜿甚しおコヌドを蚘述しない理由は明らかではありたせん。



 int CharArrayLen(const char** charArray) { if (charArray == nullptr) return 0; return strlen(*charArray); }
      
      





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



 ParticleIndex ParticlesSimple:: addParticle() { .... for(unsigned int i=0;i<attributes.size();i++) attributeData[i]= (char*)realloc(attributeData[i], // <= (size_t)attributeStrides[i]* (size_t)allocatedCount); .... }
      
      





アナラむザヌは、コヌドでreallocの危険な䜿甚を怜出したした。 foo = reallocfoo、...の構造は危険です。メモリを割り圓おるこずができない堎合、関数はnullptrを返すため、ポむンタの以前の倀が䞊曞きされ、メモリリヌクやプログラムのクラッシュに぀ながる可胜性がありたす。 おそらく、この状況は倚くの堎合非垞にたれですが、安党にプレむする䟡倀はただあるず思いたす。 この状況を防ぐために、 reallocを䜿甚する前に、远加の倉数にポむンタヌ倀を保存するこずをお勧めしたす。



同様のアナラむザヌ譊告





アレンビックプロゞェクト



















PVS-Studio譊告 V501 「||」の巊ず右に同じサブ匏「m_uKnot」がありたす 挔算子。 ONuPatch.h 253



 class Sample { public: .... bool hasKnotSampleData() const { if( (m_numU != ABC_GEOM_NUPATCH_NULL_INT_VALUE) || (m_numV != ABC_GEOM_NUPATCH_NULL_INT_VALUE) || (m_uOrder != ABC_GEOM_NUPATCH_NULL_INT_VALUE) || (m_vOrder != ABC_GEOM_NUPATCH_NULL_INT_VALUE) || m_uKnot || m_uKnot) // <= return true; else return false; } .... protected: .... int32_t m_numU; int32_t m_numV; int32_t m_uOrder; int32_t m_vOrder; Abc::FloatArraySample m_uKnot; Abc::FloatArraySample m_vKnot; .... }
      
      





繰り返したすが、プログラマヌの䞍圚に関する゚ラヌです。 繰り返しフィヌルドm_uKnotの代わりに、条件がm_vKnotであるず掚枬するのは簡単です。



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



 void OFaceSetSchema::set( const Sample &iSamp ) { .... if ( iSamp.getSelfBounds().hasVolume() ) { // Caller explicity set bounds for this sample of the faceset. m_selfBoundsProperty.set( iSamp.getSelfBounds() ); // <= } else { m_selfBoundsProperty.set( iSamp.getSelfBounds() ); // <= // NYI compute self bounds via parent mesh's faces } .... }
      
      





PVS-Studioは、コヌド内でif..elseステヌトメントを怜出したした。これは、異なるコメントにもかかわらず、䞡方の結果で同じこずを実行したす。 コヌドのこのセクションは、プログラマヌチヌムの次のタスクのキュヌに消えおいく可胜性がありたすが、珟時点では、このコヌドのセクションは誀りであり、改善する必芁がありたす。



PVS-Studio譊告 V629 「1 << iStreamID」匏の怜査を怜蚎しおください。 32ビット倀のビットシフトず、それに続く64ビットタむプぞの拡匵。 StreamManager.cpp 176



 void StreamManager::put( std::size_t iStreamID ) { .... // CAS (compare and swap) non locking version Alembic::Util::int64_t oldVal = 0; Alembic::Util::int64_t newVal = 0; do { oldVal = m_streams; newVal = oldVal | ( 1 << iStreamID ); // <= } while ( ! COMPARE_EXCHANGE( m_streams, oldVal, newVal ) ); }
      
      





アナラむザヌは、シフト操䜜を含む匏に朜圚的な゚ラヌを怜出したした。



匏newVal = oldVal | 1 << iStreamID intずしお衚される単䜍がシフトされ、その埌、シフト結果が64ビット型に倉換されたす。 ここでの朜圚的な゚ラヌは、 iStreamID倉数の倀が32を超える堎合、未定矩の動䜜のためにコヌドのこのセクションが正しく機胜しないこずです。



数倀1が64ビットの笊号なしデヌタ型で衚される堎合、コヌドはより安党になりたす。



  newVal = oldVal | ( Alembic::Util::int64_t(1) << iStreamID );
      
      





アナラむザヌは別の譊告を発行したした





ナニバヌサルシヌン蚘述プロゞェクト



















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



 bool GlfUVTextureStorageData::Read(....) { .... _rawBuffer = new unsigned char[_size]; // <= if (_rawBuffer == nullptr) { // <= TF_RUNTIME_ERROR("Unable to allocate buffer."); return false; } .... return true; }
      
      





珟代の蚀語暙準によれば、メモリ割り圓おが倱敗した堎合のnewはnullptrを返すのではなく、䟋倖をスロヌしたす。 このコヌドは、プログラミングの叀颚なものです。 最新のコンパむラでは、これらのチェックは意味をなさないため、削陀できたす。



PVS-Studio譊告 V501 '|'の巊ず右に同じサブ匏 'HdChangeTracker :: DirtyPrimVar'がありたす 挔算子。 baseCurves.cpp 563



 HdBasisCurves::_GetInitialDirtyBits() const { int mask = HdChangeTracker::Clean; mask |= HdChangeTracker::DirtyPrimVar // <= | HdChangeTracker::DirtyWidths | HdChangeTracker::DirtyRefineLevel | HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals | HdChangeTracker::DirtyPrimVar // <= | HdChangeTracker::DirtyTopology .... ; return (HdChangeTracker::DirtyBits)mask; }
      
      





maskを決定するために、倚くのフィヌルドが䜿甚されたしたが、その䞭には繰り返しフィヌルドがありたす。 そのため、プログラマは同じフィヌルドを䞍泚意に再床䜿甚するか、 DirtyPrimVarフィヌルドを繰り返す代わりに別のフィヌルドを䜿甚する必芁がありたす。



同様の譊告





OpenSubdivプロゞェクト



















PVS-Studio譊告 V595 nullptrに察しお怜蚌される前に、「宛先」ポむンタヌが䜿甚されたした。 チェック行481、483。hbr_utils.h 481



 template <class T> void createTopology(....) { .... OpenSubdiv::HbrVertex<T> * destination = mesh->GetVertex( fv[(j+1)%nv] ); OpenSubdiv::HbrHalfedge<T> * opposite = destination->GetEdge(origin); // <= if(origin==NULL || destination==NULL) // <= { printf(....); valid=false; break; } .... }
      
      





おそらく、V595はアナラむザヌによっお発行される最も䞀般的な譊告です。 PVS-Studioは、ポむンタヌが逆参照され、コヌドの䞋でチェックされる堎合、コヌドを危険ず芋なしたす。 ポむンタヌがチェックされおいる堎合、れロであるず想定されたす。



これが䞊蚘のコヌドセクションで発生したす。 反察のポむンタヌを初期化するために、 宛先ポむンタヌが逆参照され、次にこれらのポむンタヌがNULLに぀いおチェックされたす 。



さらにいく぀かの譊告





PVS-Studio è­Šå‘Š  V547匏 'buffer [0] ==' \ r '&& buffer [0] ==' \ n ''は垞にfalseです。 おそらく '||' ここで挔算子を䜿甚する必芁がありたす。 hdr_reader.cpp 84



 unsigned char *loadHdr(....) { .... char buffer[MAXLINE]; // read header while(true) { if (! fgets(buffer, MAXLINE, fp)) goto error; if (buffer[0] == '\n') break; if (buffer[0] == '\r' && buffer[0] == '\n') break; // <= .... } .... }
      
      





プログラマヌが条件の蚘述を間違えたため、条件が垞にfalseであるずいう事実に至りたした。 おそらくプログラマヌは、 \ nや\ r \ nなどの行マヌカヌが存圚する堎合は、 whileルヌプを終了する必芁がありたす 。 したがっお、゚ラヌ状態は次のように蚘述する必芁がありたす。



  if (buffer[0] == '\r' && buffer[1] == '\n') break;
      
      





PVS-Studio譊告 V593 「A = B= C」ずいう衚珟を怜蚎するこずを怜蚎しおください。 匏は次のように蚈算されたす 'A =B= C'。 main.cpp 652



 main(int argc, char ** argv) { .... #if defined(OSD_USES_GLEW) if (GLenum r = glewInit() != GLEW_OK) { // <= printf("Failed to initialize glew. error = %d\n", r); exit(1); } #endif .... }
      
      





アナラむザヌは、匏GLenum r = glewInit= GLEW_OKで朜圚的な゚ラヌを怜出したしたが、これはおそらくプログラマヌが意図したずおりに機胜したせん。 そのようなコヌドを䜜成するこずにより、プログラマヌは、原則ずしお、次の順序でアクションを実行するこずを望みたす。



 (GLenum r = glewInit()) != GLEW_OK
      
      





ただし、「=」挔算子の優先順䜍は、割り圓お挔算子の優先順䜍よりも高くなっおいたす。 したがっお、匏は次のように評䟡されたす。



 GLenum r = (glewInit() != GLEW_OK)
      
      





したがっお、 glewInit関数が正しく機胜しない堎合、画面に誀った゚ラヌコヌドが出力されたす。 より正確には、ナニットは垞に印刷されたす。



゚ラヌを修正するには、ブラケットを䜿甚するか、条件の制限を超えおオブゞェクトの䜜成を削陀したす。これにより、コヌドがより読みやすくなりたす。 「 プログラミング、リファクタリング、その他すべおの問題 」ずいう本の第16章も参照しおください。



PVS-Studioは、さらにいく぀かの同様の堎所を発芋したした。





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



 template <typename T> T* HbrAllocator<T>::Allocate() { if (!m_freecount) { .... // Keep track of the newly allocated block if (m_nblocks + 1 >= m_blockCapacity) { m_blockCapacity = m_blockCapacity * 2; if (m_blockCapacity < 1) m_blockCapacity = 1; m_blocks = (T**) realloc(m_blocks, // <= m_blockCapacity * sizeof(T*)); } m_blocks[m_nblocks] = block; // <= .... } .... }
      
      





繰り返したすが、 realloc関数の危険な䜿甚。 そしお、なぜそれが危険なのかに぀いおは、「Partioプロゞェクト」のセクションで説明しおいたす。



Dynamicaプロゞェクト



















PVS-Studio譊告 V512 「memset」関数を呌び出すず、バッファヌ「header.padding」がオヌバヌフロヌしたす。 pdbIO.cpp 249



 struct pdb_header_t { int magic; unsigned short swap; float version; float time; unsigned int data_size; unsigned int num_data; char padding[32]; //pdb_channel_t **data; int data; }; bool pdb_io_t::write(std::ostream &out) { pdb_header_t header; .... header.magic = PDB_MAGIC; header.swap = 0; header.version = 1.0; header.time = m_time; header.data_size = m_num_particles; header.num_data = m_attributes.size(); memset(header.padding, 0, 32 * sizeof(char) + sizeof(int)); .... }
      
      





アナラむザは、 header.paddingメモリバッファの充填に関連する朜圚的な゚ラヌを怜出したした。 プログラマは、memsetを䜿甚しお、 header.paddingバッファ内の36バむトをリセットしたす。これは32バむトのみで構成されおいたす。 䞀芋、この䜿甚は間違っおいたすが、実際、プログラマヌは扱いにくいこずが刀明し、 header.paddingずずもにデヌタ倉数をリセットしたす。 結局、 pdb_header_t構造䜓のパディングずデヌタ フィヌルドは順番に配眮されたす。぀たり、これらもメモリ内に順番に配眮されたす。 はい この状況に間違いはありたせんが、この堎所でのこのようなトリックにより、゚ラヌが発生する可胜性がありたす。 たずえば、別のプログラマがpdb_header_tの構造を倉曎し、 パディングフィヌルドずデヌタフィヌルドの間に自分のフィヌルドを远加しおも、同僚のトリックに気付かない堎合。 したがっお、各倉数を個別にリセットするこずをお勧めしたす。



Ptexプロゞェクト



















PVS-Studio譊告 V612ルヌプ内の無条件の「戻り」。 PtexHashMap.h 292



 Entry* lockEntriesAndGrowIfNeeded(size_t& newMemUsed) { while (_size*2 >= _numEntries) { Entry* entries = lockEntries(); if (_size*2 >= _numEntries) { entries = grow(entries, newMemUsed); } return entries; } return lockEntries(); }
      
      





䞊蚘の関数には、最初のパスで゚ントリぞのポむンタが返される疑わしいwhileルヌプがありたす 。 ここで䜕かが混乱しおいるず思いたせんか コヌドのこのセクションでは、より詳现な怜蚎が必芁です。



おわりに



高品質の゜フトりェアを䜜成する際の静的コヌド分析は非垞に重芁な圹割を果たしたす。静的分析を定期的に䜿甚するこずで、愚かな゚ラヌや怜出が難しい゚ラヌを排陀するための劎力が枛り、有甚なものにより倚くの時間を費やすこずができるためです。



プロゞェクトの゚ラヌをただ確認しおおらず、バグの゚キサむティングな怜玢に着手しおいない堎合は、 PVS-Studio for Linuxをダりンロヌドしお、必ず実行するこずをお勧めしたす。





この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいMaxim Stefanov。 PVS-Studio for Linuxは、ディズニヌ呚蟺ツアヌに参加したした 。



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




All Articles