FreeCADの゜ヌスコヌドずその「悪い」䟝存関係の確認







この蚘事は、オヌプンなFreeCADプロゞェクトの゚ラヌの抂芁を目的ずしおおり、わずかに異なる性栌を獲埗したした。 アナラむザヌの譊告の倧郚分は、䜿甚されおいるサヌドパヌティのラむブラリから発生したした。 サヌドパヌティラむブラリを積極的に䜿甚した゜フトりェア開発には、特にオヌプン゜ヌスの分野で倚くの利点がありたす。 そしお、ラむブラリの゚ラヌはそれらを拒吊する理由ではありたせん。 ただし、䜿甚するコヌドにもバグが存圚する可胜性があるこずを理解する必芁がありたす。 圌らは䌚う準備ができおいお、堎合によっおは修正する必芁があり、それによっおあなたが䜿甚するラむブラリを改善したす。



FreeCADは、3次元モデルずその投圱図を䜜成できるパラメトリック3次元゚ディタヌです。 DaimlerChrysler Corporationに勀務するFreeCAD開発者JÃŒrgenRigelは、圌のプログラムを最初の無料の機械蚭蚈ツヌルずしお䜍眮付けおいたす。 倚くの業界の専門家の間では、オヌプン゜ヌスのフレヌムワヌク内で本栌的なCADシステムを䜜成する問題が知られおおり、このプロゞェクトはそのような「完党な䟡倀」の候補です。 PVS-Studioを䜿甚しお゜ヌスコヌドを確認し、この分野のオヌプン゜ヌスプロゞェクトが少し良くなるようにしたす。 垞に1ピクセル移動するポむントに到達したり、盎線をたっすぐにしたりできないずきに、さたざたな゚ディタヌで「グリッチ」に遭遇したした。 おそらく、これらすべおの理由は、゜ヌスコヌドのタむプミスにすぎたせん。



PVS-Studioの䜕が問題になっおいたすか







FreeCADプロゞェクトはクロスプラットフォヌムであり、サむトには非垞に優れたアセンブリドキュメントがありたす。 むンストヌルされたPVS-Studioプラグむンを䜿甚しお、怜蚌のためにVisual Studio Community 2013のプロゞェクトファむルを取埗するこずは難しくありたせんでした。 しかし、最初はチェックがうたくいきたせんでした...











アナラむザヌの内郚゚ラヌの原因は、* .i拡匵子を持぀テキスト前凊理ファむルにバむナリシヌケンスが存圚するこずでした。 アナラむザヌはそのような状況を凊理できたすが、䜕か新しいこずが起こりたした。 問題は、゜ヌスファむルのコンパむルオプションのいずれかの行にありたす。

/FI"Drawing.dir/Debug//Drawing_d.pch"
      
      





コンパむルフラグ/ FI名前匷制むンクルヌドファむルず#includeディレクティブは、テキストヘッダヌファむルを含めるために䜿甚されたす。 しかし、ここでは、バむナリ圢匏の情報を含むファむルを含めようずしおいたす。 奇跡的にコンパむルしたす。 おそらくVisual C ++では、このようなファむルは単に無芖されたす。



コンパむルしない、぀たりファむルを前凊理しようずするず、Visual C ++ぱラヌを報告したす。 そしお、ここでは、PVS-Studioでデフォルトで䜿甚されおいるClangには、考え盎さずに* .iファむルずバむナリファむルが含たれおいたした。 PVS-Studioはそのようなトリックを期埅しおいなかったため、倢䞭になりたした。



危機にwhatしおいるものを明確にするために、Clangで前凊理されたファむルのフラグメントを次に瀺したす。











このフラグを䜿甚せずにプロゞェクトを慎重にチェックしたしたが、ここに間違いがあるずいう開発者の泚意を匕きたいず思いたす。



Freecad



プロゞェクトの゚ラヌの最初の䟋は、誰もがよく知っおいる理由で埗られたした。







V501 「=」挔算子の巊右には、同䞀の副次匏「surfaceTwo-> IsVRational」がありたす。 modelrefine.cpp 780

 bool FaceTypedBSpline::isEqual(const TopoDS_Face &faceOne, const TopoDS_Face &faceTwo) const { .... if (surfaceOne->IsURational() != surfaceTwo->IsURational()) return false; if (surfaceTwo->IsVRational() != surfaceTwo->IsVRational())//<= return false; if (surfaceOne->IsUPeriodic() != surfaceTwo->IsUPeriodic()) return false; if (surfaceOne->IsVPeriodic() != surfaceTwo->IsVPeriodic()) return false; if (surfaceOne->IsUClosed() != surfaceTwo->IsUClosed()) return false; if (surfaceOne->IsVClosed() != surfaceTwo->IsVClosed()) return false; if (surfaceOne->UDegree() != surfaceTwo->UDegree()) return false; if (surfaceOne->VDegree() != surfaceTwo->VDegree()) return false; .... }
      
      





䞍等匏挔算子の巊偎で、タむプミスが原因で、「surfaceOne」の代わりに間違った倉数「surfaceTwo」が芋぀かりたした。 次回、コピヌペヌストフラグメントを倧きくするために著者にアドバむスするこずは残っおいたすが、このような䟋に぀いおも説明したす=。



V517 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認162、164。taskpanelview.cpp 162

 /// @cond DOXERR void TaskPanelView::OnChange(....) { std::string temp; if (Reason.Type == SelectionChanges::AddSelection) { } else if (Reason.Type == SelectionChanges::ClrSelection) { } else if (Reason.Type == SelectionChanges::RmvSelection) { } else if (Reason.Type == SelectionChanges::RmvSelection) { } }
      
      





ただ曞かれおいる機胜に泚意を払ったのはなぜですか その理由は次のずおりです。このコヌドを䜿甚するず、次の2぀の䟋の堎合ずほずんど同じになりたす。



V517 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認しおください1465、1467。application.cpp 1465

 pair<string, string> customSyntax(const string& s) { #if defined(FC_OS_MACOSX) if (s.find("-psn_") == 0) return make_pair(string("psn"), s.substr(5)); #endif if (s.find("-display") == 0) return make_pair(string("display"), string("null")); else if (s.find("-style") == 0) return make_pair(string("style"), string("null")); .... else if (s.find("-button") == 0) //<== return make_pair(string("button"), string("null")); //<== else if (s.find("-button") == 0) //<== return make_pair(string("button"), string("null")); //<== else if (s.find("-btn") == 0) return make_pair(string("btn"), string("null")); .... }
      
      





著者がコピヌした1行を誀っお修正しなかったこずを願っおいたすが、結局、必芁なすべおの行の怜玢を远加したした。



V517 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認191、199。blendernavigationstyle.cpp 191

 SbBool BlenderNavigationStyle::processSoEvent(....) { .... else if (!press && (this->currentmode == NavigationStyle::DRAGGING)) { //<== SbTime tmp = (ev->getTime() - this->centerTime); float dci = (float)QApplication::....; if (tmp.getValue() < dci) { newmode = NavigationStyle::ZOOMING; } processed = TRUE; } else if (!press && (this->currentmode == NavigationStyle::DRAGGING)) { //<== this->setViewing(false); processed = TRUE; } .... }
      
      





しかし、私の意芋では、そのようなアプリケヌションの重倧な間違いです。 モデリング時には、マりスナビゲヌションを䜿甚しお倚くの䜜業が行われたすが、ここにそのような間違いがありたす。最初の条件が同じで最初の条件が実行されるため、最埌の条件の゜ヌスコヌドは制埡されたせん。



V523 「then」ステヌトメントは「else」ステヌトメントず同等です。 viewproviderfemmesh.cpp 695









 inline void insEdgeVec(std::map<int,std::set<int> > &map, int n1, int n2) { if(n1<n2) map[n2].insert(n1); else map[n2].insert(n1); };
      
      





条件に関係なく、1぀のアクションが垞に実行されたす。 倚分それはただこのように考えられおいた

 inline void insEdgeVec(std::map<int,std::set<int> > &map, int n1, int n2) { if(n1<n2) map[n2].insert(n1); else map[n1].insert(n2); };
      
      





最埌の行を修正したのはなぜですか このトピックに関する蚘事に興味があるかもしれたせん 最埌の行の効果 。 しかし、おそらく最初の問題を解決する必芁がありたす。 知りたせん:)。



V570 「this-> quat [3]」倉数はそれ自䜓に割り圓おられたす。 rotation.cpp 260

 Rotation & Rotation::invert(void) { this->quat[0] = -this->quat[0]; this->quat[1] = -this->quat[1]; this->quat[2] = -this->quat[2]; this->quat[3] = this->quat[3]; //<== return *this; }
      
      





「最埌の行」に぀いおの詳现。 アナラむザヌは、最埌の行にマむナス蚘号がないため、泚意を払っおいたす。 しかし、ここでは間違いなく゚ラヌを話すこずは䞍可胜です。おそらく、そのような倉換を実装するずき、圌らは4番目のコンポヌネントが倉わらないこずを匷調したかったのです。



V576圢匏が正しくありたせん 。 'fprintf'関数を呌び出すずきに、異なる数の実匕数が予期されたす。 予想2.珟圚3. memdebug.cpp 222

 int __cdecl MemDebug::sAllocHook(....) { .... if ( pvData != NULL ) fprintf( logFile, " at %p\n", pvData ); else fprintf( logFile, "\n", pvData ); //<== .... }
      
      





このようなコヌドは意味がありたせん。 ポむンタヌがヌルの堎合、未䜿甚のパラメヌタヌを関数に枡すこずなく、単に改行を印刷できたす。



V596オブゞェクトは䜜成されたしたが、䜿甚されおいたせん。 「throw」キヌワヌドが欠萜しおいる可胜性がありたすthrow ExceptionFOO; waypointpyimp.cpp 231









 void WaypointPy::setTool(Py::Int arg) { if((int)arg.operator long() > 0) getWaypointPtr()->Tool = (int)arg.operator long(); else Base::Exception("negativ tool not allowed!"); }
      
      





䟋倖タむプのオブゞェクトはコヌド内に䜜成されたすが、䜿甚されたせん。 どうやら「スロヌ」キヌワヌドが欠萜しおいるようです

 void WaypointPy::setTool(Py::Int arg) { if((int)arg.operator long() > 0) getWaypointPtr()->Tool = (int)arg.operator long(); else throw Base::Exception("negativ tool not allowed!"); }
      
      





さらにいく぀かのそのような堎所

V599 「カヌブ」クラスには仮想関数が含たれおいたすが、仮想デストラクタは存圚したせん。 constraints.cpp 1442

 class Curve { //a base class for all curve-based //objects (line, circle/arc, ellipse/arc) //<== public: virtual DeriVector2 CalculateNormal(....) = 0; virtual int PushOwnParams(VEC_pD &pvec) = 0; virtual void ReconstructOnNewPvec (....) = 0; virtual Curve* Copy() = 0; }; class Line: public Curve //<== { public: Line(){} Point p1; Point p2; DeriVector2 CalculateNormal(Point &p, double* derivparam = 0); virtual int PushOwnParams(VEC_pD &pvec); virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); virtual Line* Copy(); };
      
      





䜿甚法

 class ConstraintAngleViaPoint : public Constraint { private: inline double* angle() { return pvec[0]; }; Curve* crv1; //<== Curve* crv2; //<== .... }; ConstraintAngleViaPoint::~ConstraintAngleViaPoint() { delete crv1; crv1 = 0; //<== delete crv2; crv2 = 0; //<== }
      
      





基本クラスでは「曲線」仮想関数が宣蚀されおいたすが、デフォルトで䜜成されるデストラクタは宣蚀されおいたせん。 そしおもちろん、仮想ではありたせん ぀たり、このようなナヌスケヌスでは、子クラスぞのポむンタヌが基本クラスぞのポむンタヌに栌玍されおいる堎合、このクラスから継承するオブゞェクトは完党にはクリヌンアップされたせん。 コメントから刀断するず、基本クラスには倚くの継承クラスがありたす。たずえば、この䟋の「Line」クラスなどです。



V655ストリングは連結されたしたが、䜿甚されたせん。 匏を調べるこずを怜蚎しおください。 propertyitem.cpp 1013

 void PropertyVectorDistanceItem::setValue(const QVariant& variant) { if (!variant.canConvert<Base::Vector3d>()) return; const Base::Vector3d& value = variant.value<Base::Vector3d>(); Base::Quantity q = Base::Quantity(value.x, Base::Unit::Length); QString unit = QString::fromLatin1("('%1 %2'").arg(....; q = Base::Quantity(value.y, Base::Unit::Length); unit + QString::fromLatin1("'%1 %2'").arg(....; //<== setPropertyValue(unit); }
      
      





アナラむザヌは、無意味な行の远加を怜出したした。 よく芋るず、おそらく単玔な加算の代わりに「+ =」挔算子を䜿甚したかったでしょう。 そのようなコヌドは理にかなっおいたす。



V595 nullptrに察しお怜蚌される前に、「ルヌト」ポむンタヌが䜿甚されたした。 行を確認293、294。view3dinventorexamples.cpp 293

 void LightManip(SoSeparator * root) { SoInput in; in.setBuffer((void *)scenegraph, std::strlen(scenegraph)); SoSeparator * _root = SoDB::readAll( &in ); root->addChild(_root); //<== if ( root == NULL ) return; //<== root->ref(); .... }
      
      





間違った堎所でポむンタヌをチェックする1぀の䟋、他の堎所はこちらです

オヌプンカスケヌドラむブラリ



V519 「myIndex [1]」倉数には、倀が2回連続しお割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください60、61。brepmesh_pairofindex.hxx 61

 //! Prepends index to the pair. inline void Prepend(const Standard_Integer theIndex) { if (myIndex[1] >= 0) Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex...."); myIndex[1] = myIndex[0]; myIndex[1] = theIndex; }
      
      





この䟋では、むンデックス1の配列芁玠 'myIndex'の倀が䞊曞きされたした。

 myIndex[1] = myIndex[0]; myIndex[0] = theIndex;
      
      





SALOME Smeshモゞュヌル



V501 「&&」挔算子の巊右には、同䞀のサブ匏「0 <= theParamsHint.Y」がありたす。 smesh_block.cpp 661

 bool SMESH_Block::ComputeParameters(const gp_Pnt& thePoint, gp_XYZ& theParams, const int theShapeID, const gp_XYZ& theParamsHint) { .... bool hasHint = ( 0 <= theParamsHint.X() && theParamsHint.X() <= 1 && 0 <= theParamsHint.Y() && theParamsHint.Y() <= 1 && 0 <= theParamsHint.Y() && theParamsHint.Y() <= 1 ); //<== .... }
      
      





十分な怜蚌.Zがないこずは明らかです。 このクラスにはこのような機胜があり、「gp_XYZ」ずも呌ばれたす。



V503これは無意味な比范ですポむンタヌ<0。driverdat_r_smds_mesh.cpp 55

 Driver_Mesh::Status DriverDAT_R_SMDS_Mesh::Perform() { .... FILE* aFileId = fopen(file2Read, "r"); if (aFileId < 0) { fprintf(stderr, "....", file2Read); return DRS_FAIL; } .... }
      
      





ポむンタヌをれロより小さくするこずはできたせん。 本やむンタヌネットにあるfopen関数を䜿甚した最も単玔な䟋でも、関数の倀は==たたは=でNULLず比范されたす。



そのようなコヌドがどのように衚瀺されるのか疑問に思いたした。 しかし、私の同僚のAndrei Karpovは、 open関数がか぀お䜿甚されおいたコヌドをリファクタリングするずきにこれが頻繁に起こるこずを瀺唆したした。 この関数は、ケヌスで-1を返し、比范<0は非垞に適切です。 プログラムのリファクタリングたたは移怍の過皋で、この関数はfopenに眮き換えられたすが、チェックの修正を忘れおいたす。



別のそのような堎所

V562ブヌル型の倀を12 :! MyType == SMESHDS_MoveNodeの倀ず比范するのは奇劙です。 smeshds_command.cpp 75

 class SMESHDS_EXPORT SMESHDS_Command { .... private: SMESHDS_CommandType myType; .... }; enum SMESHDS_CommandType { SMESHDS_AddNode, SMESHDS_AddEdge, SMESHDS_AddTriangle, SMESHDS_AddQuadrangle, .... }; void SMESHDS_Command::MoveNode(....) { if (!myType == SMESHDS_MoveNode) //<== { MESSAGE("SMESHDS_Command::MoveNode : Bad Type"); return; } .... }
      
      





「SMESHDS_CommandType」ずいう名前の列挙があり、倚くの定数がありたす。 アナラむザヌが誀ったチェックを怜出したした。このタむプの倉数は名前付き定数ず比范されたすが、ここで吊定蚘号は䜕をしたすか?? ほずんどの堎合、チェックは次のようになりたす。

 if (myType != SMESHDS_MoveNode) //<== { MESSAGE("SMESHDS_Command::MoveNode : Bad Type"); return; }
      
      





残念ながら、このような゚ラヌメッセヌゞを含むチェックは別の20箇所にコピヌされたした 。完党なリストは次のずおりです FreeCAD_V562.txt



V567未定矩の動䜜。 匕数評䟡の順序は、「スプラむス」関数に察しお定矩されおいたせん。 'outerBndPos'倉数は、シヌケンスポむント間で2回䜿甚されおいる間に倉曎されたす。 smesh_pattern.cpp 4260

 void SMESH_Pattern::arrangeBoundaries (....) { .... if ( outerBndPos != boundaryList.begin() ) boundaryList.splice( boundaryList.begin(), boundaryList, outerBndPos, //<== ++outerBndPos ); //<== }
      
      





実際、アナラむザヌはここにはたったくありたせん。 未定矩の動䜜はここにはありたせん。 しかし、゚ラヌがあるため、譊告は無駄ではありたせんでした。 C ++暙準は、実際の関数匕数が評䟡される順序に制限を課したせん。 したがっお、どの倀が関数に枡されるかはわかりたせん。



簡単な䟋で説明したす。

 int a = 5; printf("%i, %i", a, ++a);
      
      





このコヌドは、「5、6」ず「6、6」の䞡方を出力できたす。結果は、コンパむラずその蚭定によっお異なりたす。



V663無限ルヌプが可胜です。 「cin.eof」条件は、ルヌプから抜けるには䞍十分です。 「cin.fail」関数呌び出しを条件匏に远加するこずを怜蚎しおください。 unv_utilities.hxx 63

 inline bool beginning_of_dataset(....) { .... while( ((olds != "-1") || (news == "-1") ) && !in_file.eof() ){ olds = news; in_file >> news; } .... }
      
      





「std :: istream」クラスを䜿甚する堎合、「eof」関数を呌び出すだけではルヌプを完了できたせん。 デヌタの読み取り䞭に障害が発生した堎合、「eof」関数を呌び出すず、垞に倀「false」が返されたす。 この堎合にルヌプを完了するには、関数 'fail'によっお返された倀の远加チェックが必芁です。



V595 nullptrに察しお怜蚌される前に、「anElem」ポむンタヌが䜿甚されたした。 行を確認しおください1950、1951。smesh_controls.cpp 1950

 bool ElemGeomType::IsSatisfy( long theId ) { if (!myMesh) return false; const SMDS_MeshElement* anElem = myMesh->FindElement( theId ); const SMDSAbs_ElementType anElemType = anElem->GetType(); if (!anElem || (myType != SMDSAbs_All && anElemType != myType)) return false; const int aNbNode = anElem->NbNodes(); .... }
      
      





ポむンタ「anElem」は、有効性を確認するよりも1行䞊で参照解陀されたす。



このプロゞェクトの類䌌した堎所

Boost C ++ラむブラリ



V567未定矩の動䜜。 'this-> n_'倉数は、シヌケンスポむント間で2回䜿甚されおいる間に倉曎されたす。 regex_token_iterator.hpp 63

 template<typename BidiIter> struct regex_token_iterator_impl : counted_base<regex_token_iterator_impl<BidiIter> > { .... if(0 != (++this->n_ %= (int)this->subs_.size()) || .... { .... } .... }
      
      







=挔算子のどのオペランドが最初に評䟡されるかは䞍明です。 したがっお、匏が正しく機胜するかどうかは、運次第です。



おわりに



静的アナラむザヌを詊しお実装し、プロゞェクトず定期的に䜿甚するサヌドパヌティラむブラリを定期的にチェックしおください。 これにより、新しいコヌドを䜜成するずきず、叀いコヌドをサポヌトするずきの時間が節玄されたす。





英語を話す聎衆ずこの蚘事を共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいSvyatoslav Razmyslov。 FreeCADの゜ヌスコヌドずその「病気」の䟝存関係の分析 。



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




All Articles