Bad Toonz 2D Animation Package Code

    Recently it became known that Digital Video, the creators of the TOONZ project, and the Japanese publisher DWANGO signed an agreement on the acquisition by the company DWANGO of the Toonz project, software for creating 2D animation.

    Under the terms of the agreement signed between the parties, public access will be open to OpenToonz, a project developed by Toonz. It will also include some elements developed by Studio Ghibli, which in turn are active users of these programs. With their help, for example, Studio Ghibli created "Howl's Walking Castle", "Spirited Away", "Ponyo Fish", as well as many other paintings. Among them is also the Futurama cartoon, which inspired me to write this revealing article about the OpenToonz source code.

    Introduction


    OpenToonz is a 2D animation software. The basis is the Toonz project, which was developed by the Italian company Digital Video. Adapting this program, Studio Ghibli has been successfully using it for many years. In addition to animated films, the project was also involved in computer games - Discworld 2 and Claw.

    It is worth noting that the price of the package so far has been $ 10,000, but the quality of the code leaves much to be desired. This project is a godsend for any static analyzer. The size of the OpenToonz source code is approximately 1/10 of the FreeBSD kernel, in which more than 40 serious errors were found using the PVS-Studio analyzer, but there are much more!

    OpenToonz was tested in Visual Studio 2013 using the PVS-Studio version 6.03 static analyzer , which is actively developed and supports C / C ++ / C # languages, and various build systems. Even during the compilation of the project, I was wary when the number of compiler warnings began to grow rapidly. At the end of the assembly, their number was 1211 pieces! Those. quality control of the source code was almost neglected. Moreover, some compiler warnings were disabled using the #pragma warning directive, but even here mistakes were made, about which I will discuss below. This article is distinguished by the fact that many real errors were found in the project, which are usually made by beginners who are just starting to learn C / C ++. I will begin the description of the analyzer warnings found with memory and pointer errors.

    Incorrect memory handling




    V611 The memory was allocated using 'new' operator but was released using the 'free' function. Consider inspecting operation logics behind the 'row' variable. motionblurfx.cpp 288
    template 
    void doDirectionalBlur(....)
    {
      T *row, *buffer;
      ....
      row = new T[lx + 2 * brad + 2]; // <=
      if (!row)
        return;
      memset(row, 0, (lx + 2 * brad + 2) * sizeof(T));
      ....
      free(row);                      // <=
      r->unlock();
    }

    Here, the analyzer found that dynamic memory is allocated and freed up in incompatible ways. After calling the new [] operator, you need to free the memory using the delete [] operator . It is with two square brackets! I focus on this for a reason - see the following example.

    V611 The memory was allocated using 'new T []' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] uPrime;'. tstroke.cpp 3353
    double *reparameterize3D(....)
    {
      double *uPrime = new double[size]; // <=
      for (int i = 0; i < size; i++) {
        uPrime[i] = NewtonRaphsonRootFind3D(....);
        if (!_finite(uPrime[i])) {
          delete uPrime;                 // <=
          return 0;
        }
      }
      ....
    }

    In C ++, the operators new / delete and new [] / delete [] are used in conjunction with each other. Using different operators to allocate and free dynamic memory is a mistake. In the above code, the memory occupied by the uPrime array will not be correctly freed.

    Unfortunately, this is not the only place. I wrote out 20 more places to the OpenToonz_V611.txt file .

    V554 Incorrect use of auto_ptr. The memory allocated with 'new []' will be cleaned using 'delete'. screensavermaker.cpp 29
    void makeScreenSaver(....)
    {
      ....
      std::auto_ptr swf(new char[swfSize]);
      ....
    }

    Before us is an alternative variant of the considered error, but here the delete operator is “hidden” inside the smart pointer std :: auto_ptr . This also leads to undefined program behavior.

    To fix the error, you must specify that the delete [] operator should be used .

    The correct version of the code:
    std::unique_ptr swf(new char[swfSize]);
    

    V599 The destructor was not declared as a virtual one, although the 'TTileSet' class contains virtual functions. cellselection.cpp 891
    void redo() const
    {
      insertLevelAndFrameIfNeeded();
      TTileSet *tiles;  // <=
      bool isLevelCreated;
      pasteRasterImageInCellWithoutUndo(...., &tiles, ....);
      delete tiles;     // <=
      TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
    }

    Now let's talk about memory leaks and incomplete destruction of objects. In this example, objects inherited from the TTileSet class will not be completely destroyed . TTileSet

    class description :
    class DVAPI TTileSet
    {
      ....
    protected:
      TDimension m_srcImageSize;
      typedef std::vector Tiles;
      Tiles m_tiles;
    public:
      TTileSet(const TDimension &dim) : m_srcImageSize(dim)
      {
      }
      ~TTileSet();      // <=
      ....
      virtual void add(const TRasterP &ras, TRect rect) = 0;
      ....
      virtual TTileSet *clone() const = 0;
    };

    The class contains pure virtual functions and is abstract. You cannot create objects of this class, so it is used only by derived classes. Thus, due to the lack of a virtual destructor, TTileSet (there is a destructor, but it is not marked as virtual), all derived classes will not be completely cleared.

    In the OpenToonz source code, I found several classes that inherit from TTileSet :
    class DVAPI TTileSetCM32 : public TTileSet
    class DVAPI TTileSetCM32 : public TTileSet
    class DVAPI TTileSetFullColor : public TTileSet
    class DVAPI Tile : public TTileSet::Tile

    Each object of these classes (or classes inherited from them) will not be completely destroyed. Formally, this will lead to undefined program behavior; in practice, this is likely to lead to a leak of memory and other resources.

    Developers need to check out two more similar places:
    • V599 The virtual destructor is not present, although the 'MessageParser' class contains virtual functions. tipcsrv.cpp 91
    • V599 The virtual destructor is not present, although the 'ColumnToCurveMapper' class contains virtual functions. functionselection.cpp 278

    Dangerous use of pointers




    V503 This is a nonsensical comparison: pointer <0. styleselection.cpp 104
    bool pasteStylesDataWithoutUndo(....)
    {
      ....
      if (palette->getStylePage(styleId) < 0) { // <=
        // styleId non e' utilizzato: uso quello
        // (cut/paste utilizzato per spostare stili)
        palette->setStyle(styleId, style);
      } else {
        // styleId e' gia' utilizzato. ne devo prendere un altro
        styleId = palette->getFirstUnpagedStyle();
        if (styleId >= 0)
          palette->setStyle(styleId, style);
        else
          styleId = palette->addStyle(style);
      }
      ....
    }

    The getStylePage () function returns a pointer to a certain page: TPalette :: Page * . Such a comparison with zero does not make sense. After searching for the use of the getStylePage () function , I found that in all other cases, the result of this function is checked only for equality and inequality to zero, and an error was made at this point.

    V522 Dereferencing of the null pointer 'region' might take place. Check the logical condition. palettecmd.cpp 102
    bool isStyleUsed(const TVectorImageP vi, int styleId)
    {
      ....
      TRegion *region = vi->getRegion(i);
      if (region || region->getStyle() != styleId)
        return true;
      ....
    }

    Most likely, the operators '&&' and '||' were confused in this code fragment. Otherwise, if the region pointer is zero, then it will be dereferenced.

    V614 Potentially uninitialized pointer 'socket' used. Consider checking the first actual argument of the 'connect' function. tmsgcore.cpp 36
    void TMsgCore::OnNewConnection() //server side
    {
      QTcpSocket *socket;
      if (m_tcpServer)                                 // <=
        socket = m_tcpServer->nextPendingConnection(); // <=
      assert(socket);
      bool ret = connect(socket, ....);                // <=
      ret = ret && connect(socket, ....);              // <=
      assert(ret);
      m_sockets.insert(socket);
    }

    The analyzer detected the potential use of an uninitialized socket pointer . If the m_tcpServer variable is false, then the pointer will not be initialized. In this uninitialized form, it can be passed to the connect () function.

    V595 The 'batchesTask' pointer was utilized before it was verified against nullptr. Check lines: 1064, 1066. batches.cpp 1064
    void BatchesController::update()
    {
      ....
      TFarmTask *batchesTask = getTask(batchesTaskId);   // <=
      TFarmTask farmTask = *batchesTask;                 // <=
      if (batchesTask) {                                 // <=
        QString batchesTaskParentId = batchesTask->m_parentId;
        m_controller->queryTaskInfo(farmTaskId, farmTask);
        int chunkSize = batchesTask->m_chunkSize;
        *batchesTask = farmTask;
        batchesTask->m_chunkSize = chunkSize;
        batchesTask->m_id = batchesTaskId;
        batchesTask->m_parentId = batchesTaskParentId;
      }
      ....
    }

    There are many places in the code where dereferencing of a null pointer could potentially occur. Usually, an appropriate check is present, but one or more places where the pointer is used remains unsafe anyway. For example, in this code snippet there is a batchesTask check , but before the check, one pointer dereference will already be performed.

    I wrote out 29 more similar places in the OpenToonz_V595.txt file .

    String Errors




    V530 The return value of function 'toUpper' is required to be utilized. sceneviewerevents.cpp 847
    void SceneViewer::keyPressEvent(QKeyEvent *event)
    {
      ....
      QString text = event->text();
      if ((event->modifiers() & Qt::ShiftModifier))
        text.toUpper();
      ....
    }

    The toUpper () method does not change the string 'text'. In the documentation, it is described as QString QString :: toUpper () const, i.e. is a constant method.

    The correct version of the code:
    QString text = event->text();
      if ((event->modifiers() & Qt::ShiftModifier))
        text = text.toUpper();

    The code contains three more functions whose return value is not used. All these places require correction:
    • V530 The return value of function 'left' is required to be utilized. tfarmserver.cpp 569
    • V530 The return value of function 'ftell' is required to be utilized. tiio_bmp.cpp 804
    • V530 The return value of function 'accumulate' is required to be utilized. bendertool.cpp 374

    V614 Uninitialized iterator 'it1' used. fxcommand.cpp 2096
    QString DeleteLinksUndo::getHistoryString()
    {
      ....
      std::list::const_iterator it1; // <=
      std::list::const_iterator ft;
      for (ft = m_terminalFxs.begin(); ft != ....end(); ++ft) {
        if (ft != m_terminalFxs.begin())
          str += QString(",  ");
        str += QString("%1- -Xsheet")
              .arg(QString::fromStdWString((*it1)->getName())); // <=
      }
      ....
    }

    String operations use the uninitialized iterator it1 . Most likely, in one place they forgot to replace it with the ft iterator .

    V642 Saving the '_wcsicmp' function result inside the 'char' type variable is inappropriate. The significant bits could be lost breaking the program's logic. tfilepath.cpp 328
    bool TFilePath::operator<(const TFilePath &fp) const
    {
      ....
      char differ;
      differ = _wcsicmp(iName.c_str(), jName.c_str());
      if (differ != 0)
        return differ < 0 ? true : false;
      ....
    }

    The _wcsicmp function returns the following values ​​of type int :
    • <0 - string1 less then string2 ;
    • 0 - string1 identical to string2 ;
    • > 0 - string1 greater than string2 .

    Note. '> 0' means any numbers, not at all 1. These numbers can be: 2, 3, 100, 256, 1024, 5555, and so on. The result of the _wcsicmp function may not fit into a variable of type char, which is why the comparison operator will return an unexpected result.

    V643 Unusual pointer arithmetic: "\\" + v [i]. The value of the 'char' type is being added to the string pointer. tstream.cpp 31
    string escape(string v)
    {
      int i = 0;
      for (;;) {
        i = v.find_first_of("\\\'\"", i);
        if (i == (int)string::npos)
          break;
        string h = "\\" + v[i]; // <=
        v.insert(i, "\\");
        i = i + 2;
      }
      return v;
    }

    The analyzer detected an error related to adding a character constant to a string literal. It was expected that a character will be added to the string, but a numeric value is added to the string pointer, which leads to the exit of the string literal and an unexpected result.

    To make it clearer, here is what this code is equivalent to:
    const char *p1 = "\\";
    const int delta = v[i];
    const char *p2 = *p1 + delta;
    string h = p2;

    The correct version of the code:
    string h = string("\\") + v[i];

    V655 The strings were concatenated but are not utilized. Consider inspecting the 'alias + "]"' expression. plasticdeformerfx.cpp 150
    string PlasticDeformerFx::getAlias(....) const
    {
      std::string alias(getFxType());
      alias += "[";
      ....
      if (sd)
        alias += ", "+toString(sd, meshColumnObj->paramsTime(frame));
      alias + "]"; // <=
      return alias;
    }

    The analyzer detected an expression whose result is not used. Most likely, the operator '+' was accidentally written in this function, instead of '+ ='. As a result of this, the square bracket at the end is not added to the alias line , as the programmer planned.

    Invalid exceptions




    V596 The object was created but it is not being used. The 'throw' keyword could be missing: throw domain_error (FOO); pluginhost.cpp 1486
    void Loader::doLoad(const QString &file)
    {
      ....
      int ret = pi->ini_(host);
      if (ret) {
        delete host;
        std::domain_error("failed initialized: error on ....");
      }
      ....
    }

    In the function, the throw keyword is accidentally forgotten . Consequence - this code does not throw an exception in case of an error. Corrected version of the code:
    throw std::domain_error("failed initialized: error on ....");

    V746 Type slicing. An exception should be caught by reference rather than by value. iocommand.cpp 1620
    bool IoCmd::saveLevel(....)
    {
      ....
      try {
        sl->save(fp, TFilePath(), overwritePalette);
      } catch (TSystemException se) { // <=
        QApplication::restoreOverrideCursor();
        MsgBox(WARNING, QString::fromStdWString(se.getMessage()));
        return false;
      } catch (...) {
        ....
      }
      ....
    }

    The analyzer detected a potential error related to catching an exception by value. This means that using the copy constructor, a new se object of type TSystemException will be constructed . This will lose some of the exception information that was stored in classes inherited from TSystemException .

    Similar suspicious places:
    • V746 Type slicing. An exception should be caught by reference rather than by value. iocommand.cpp 2650
    • V746 Type slicing. An exception should be caught by reference rather than by value. projectpopup.cpp 522
    • V746 Type slicing. An exception should be caught by reference rather than by value. projectpopup.cpp 537
    • V746 Type slicing. An exception should be caught by reference rather than by value. projectpopup.cpp 635
    • V746 Type slicing. An exception should be caught by reference rather than by value. tlevel_io.cpp 130
    • V746 Type slicing. An exception should be caught by reference rather than by value. calligraph.cpp 161
    • V746 Type slicing. An exception should be caught by reference rather than by value. calligraph.cpp 165
    • V746 Type slicing. An exception should be caught by reference rather than by value. patternmap.cpp 210
    • V746 Type slicing. An exception should be caught by reference rather than by value. patternmap.cpp 214
    • V746 Type slicing. An exception should be caught by reference rather than by value. patternmap.cpp 218
    • V746 Type slicing. An exception should be caught by reference rather than by value. scriptbinding_level.cpp 221

    Invalid conditions




    V547 Expression '(int) startOutPoints.size ()% 2! = 2' is always true. rasterselection.cpp 852
    TStroke getIntersectedStroke(TStroke &stroke, TRectD bbox)
    {
      ....
      for (t = 0; t < (int)outPoints.size(); t++)
        addPointToVector(...., (int)startOutPoints.size() % 2 != 2);
      ....
    }

    An interesting mistake. Most likely, they planned to determine whether the value of size () is even or odd. Therefore, the remainder of dividing by 2 must be compared with zero.

    V502 Perhaps the '?:' Operator works in a different way than it was expected. The '?:' Operator has a lower priority than the '+' operator. igs_motion_wind_pixel.cpp 127
    void rgb_to_lightness_(
      const double re, const double gr, const double bl, double &li)
    {
      li=((re < gr) ? ((gr < bl) ? bl : gr) : ((re < bl) ? bl : re) +
                                (gr < re)
                              ? ((bl < gr) ? bl : gr)
                              : ((bl < re) ? bl : re)) / 2.0;
    }

    In this code fragment, an error was made related to the priority of the ternary operator ':?'. Its priority is lower than that of the addition operator. As a result, if the condition (re <gr) is false, then further calculations are performed incorrectly: real variables begin to add up with logical ones.

    Never use multiple ternary operators together - this is the shortest way to add an error to your code.

    V590 Consider inspecting the 'state == (- 3) || state! = 0 'expression. The expression is excessive or contains a misprint. psdutils.cpp 174
    int psdUnzipWithoutPrediction(....)
    {
      ....
      do {
        state = inflate(&stream, Z_PARTIAL_FLUSH);
        if (state == Z_STREAM_END)
          break;
        if (state == Z_DATA_ERROR || state != Z_OK) // <=
          break;
      } while (stream.avail_out > 0);
      ....
    }

    The condition that is marked with an arrow does not depend on the result of the subexpression “state == Z_DATA_ERROR”. This can be easily verified if we construct the truth table of the entire conditional expression.

    Copy-paste programming




    V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 1448, 1454. tcenterlineskeletonizer.cpp 1448
    inline void Event::processVertexEvent()
    {
      ....
      if (newLeftNode->m_concave) {        // <=
        newLeftNode->m_notOpposites = m_generator->m_notOpposites;
        append, vector::....
        newLeftNode->m_notOpposites.push_back(newRightNode->m_edge);
        newLeftNode->m_notOpposites.push_back(newRightNode->....);
      } else if (newLeftNode->m_concave) { // <=
        newRightNode->m_notOpposites = m_generator->m_notOpposites;
        append, vector::....
        newRightNode->m_notOpposites.push_back(newLeftNode->m_edge);
        newRightNode->m_notOpposites.push_back(newLeftNode->....);
      }
      ....
    }

    The newLeftNode and newRightNode variables in the conditions are mixed up here . As a result of such an error, the else branch is never executed. Most likely, one of the conditions should be like this: if (newRightNode-> m_concave) .

    V501 There are identical sub-expressions to the left and to the right of the '||' operator: m_cutLx || m_cutLx canvassizepopup.cpp 271
    bool m_cutLx, m_cutLy;
    void PeggingWidget::on00()
    {
     ....
     m_11->setIcon(...).rotate(m_cutLx || m_cutLx ? -90 : 90),....));
     ....
    }

    The code contains two logical variables: m_cutLx and m_cutLy , which differ by only one last letter. The above example uses the same variable m_cutLx . Most likely, in one of them a typo was made.

    V501 There are identical sub-expressions 'parentTask-> m_status == Aborted' to the left and to the right of the '||' operator. tfarmcontroller.cpp 1857
    void FarmController::taskSubmissionError(....)
    {
      ....
      if (parentTask->m_status == Aborted || // <=
          parentTask->m_status == Aborted) { // <=
          parentTask->m_completionDate = task->m_completionDate;
          if (parentTask->m_toBeDeleted)
            m_tasks.erase(itParent);
      }
      ....
    }

    The analyzer found two identical comparisons with the Aborted constant . After searching the file, on line 2028 of this file I found an identical code block, but with this condition:
    if (parentTask->m_status == Completed ||
        parentTask->m_status == Aborted) {

    Most likely, in this place the conditional expression should be similar.

    V501 There are identical sub-expressions 'cornerCoords.y> upperBound' to the left and to the right of the '||' operator. tellipticbrush.cpp 1020
    template 
    void tellipticbrush::OutlineBuilder::addMiterSideCaps(....)
    {
      ....
      if (cornerCoords == TConsts::napd ||
        cornerCoords.x < lowerBound || cornerCoords.y > upperBound ||
        cornerCoords.y < lowerBound || cornerCoords.y > upperBound) {
        ....
      }
      ....
    }

    Then the programmer made a small typo, using y instead of x .

    Six more errors made as a result of copy-paste programming , I will not describe, but I will give a list. These places must be checked by the developers:
    • V501 There are identical sub-expressions 's.m_repoStatus == "modified"' to the left and to the right of the '||' operator. svnupdatedialog.cpp 210
    • V501 There are identical sub-expressions 'm_lineEdit->hasFocus()' to the left and to the right of the '||' operator. framenavigator.cpp 44
    • V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 750, 825. tpalette.cpp 750
    • V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 123, 126. igs_density.cpp 123
    • V523 The 'then' statement is equivalent to the 'else' statement. typetool.cpp 813
    • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: Comma. tgrammar.cpp 731

    Разные ошибки




    V665 Possibly, the usage of '#pragma warning (default: X)' is incorrect in this context. The '#pragma warning (push / pop)' should be used instead. Check lines: 20, 205. tspectrum.h 205
    #ifdef WIN32
    #pragma warning(disable : 4251)
    #endif
    ....
    #ifdef WIN32
    #pragma warning(default : 4251)
    #endif

    This is how the compiler warnings are turned off, which they nevertheless paid attention to in this project. The error is that the #pragma warning (default: X) directive does not include a warning, but sets it to the DEFAULT state, which may not be what the programmer expects.

    Corrected version of the code:
    #ifdef WIN32
    #pragma warning(push)
    #pragma warning(disable : 4251)
    #endif
    ....
    #ifdef WIN32
    #pragma warning(pop)
    #endif

    V546 Member of a class is initialized by itself: 'm_subId (m_subId)'. tfarmcontroller.cpp 572
    class TaskId
    {
      int m_id;
      int m_subId;
    public:
      TaskId(int id, int subId = -1) : m_id(id), m_subId(m_subId){};

    An interesting error in the class initialization list. The m_subld field is initialized by itself, although you most likely wanted to write m_subId (subId) .

    V557 Array overrun is possible. The '9' index is pointing beyond array bound. tconvolve.cpp 123
    template 
    void doConvolve_cm32_row_9_i(....)
    {
      TPixel32 val[9];                                  // <=
      ....
      for (int i = 0; i < 9; ++i) {                     // <= OK
        ....
        else if (tone == 0)
          val[i] = inks[ink];
        else
          val[i] = blend(....);
      }
      pixout->r = (typename PIXOUT::Channel)((
        val[1].r * w1 + val[2].r * w2 + val[3].r * w3 +
        val[4].r * w4 + val[5].r * w5 + val[6].r * w6 +
        val[7].r * w7 + val[8].r * w8 + val[9].r * w9 + // <= ERR
        (1 << 15)) >> 16);
      pixout->g = (typename PIXOUT::Channel)((
        val[1].g * w1 + val[2].g * w2 + val[3].g * w3 +
        val[4].g * w4 + val[5].g * w5 + val[6].g * w6 +
        val[7].g * w7 + val[8].g * w8 + val[9].g * w9 + // <= ERR
        (1 << 15)) >> 16);
      pixout->b = (typename PIXOUT::Channel)((
        val[1].b * w1 + val[2].b * w2 + val[3].b * w3 +
        val[4].b * w4 + val[5].b * w5 + val[6].b * w6 +
        val[7].b * w7 + val[8].b * w8 + val[9].b * w9 + // <= ERR
        (1 << 15)) >> 16);
      pixout->m = (typename PIXOUT::Channel)((
        val[1].m * w1 + val[2].m * w2 + val[3].m * w3 +
        val[4].m * w4 + val[5].m * w5 + val[6].m * w6 +
        val[7].m * w7 + val[8].m * w8 + val[9].m * w9 + // <= ERR
        (1 << 15)) >> 16);
      ....
    }

    A large piece of code in which someone accesses the val array , consisting of 9 elements, at an index of 1 to 9. Although there is a loop nearby that correctly accesses the array at an index of 0 to 8.

    V556 The values ​​of different enum types are compared: m_action! = EDIT_SEGMENT. Types: Action, CursorType. controlpointeditortool.cpp 257
    enum Action { NONE,
                  RECT_SELECTION,
                  CP_MOVEMENT,
                  SEGMENT_MOVEMENT,
                  IN_SPEED_MOVEMENT,
                  OUT_SPEED_MOVEMENT };
    enum CursorType { NORMAL,
                      ADD,
                      EDIT_SPEED,
                      EDIT_SEGMENT,
                      NO_ACTIVE };
    void ControlPointEditorTool::drawMovingSegment()
    {
      int beforeIndex = m_moveSegmentLimitation.first;
      int nextIndex = m_moveSegmentLimitation.second;
      if (m_action != EDIT_SEGMENT || // <=
          beforeIndex == -1 ||
          nextIndex == -1 ||
          !m_moveControlPointEditorStroke.getStroke())
        return;
      ....
    }

    The analyzer detected a comparison of enum values ​​of different types. Using code search, I also found that the field of class m_action is initialized with the correct type, and in this place is compared with a constant of another type.

    Conclusion




    As I already mentioned, the OpenToonz project is a godsend for any static code analyzer: it contains a lot of serious errors for such a small project. The article not only did not include all analyzer messages, it did not even include many interesting and serious warnings due to their large number. Of course, I will write about checking the developer forum. Perhaps they will be interested in improving the quality of the code.

    The intention to open the code of its software product Universal Scene Description (USD) was also announced by Pixar. I will look forward to this event.

    I suggest everyone to try PVS-Studio on their C / C ++ / C # projects. The analyzer runs in a Windows environment and supports various build systems.


    If you want to share this article with an English-speaking audience, then please use the link to the translation: Svyatoslav Razmyslov. Toonz code leaves much to be desired .

    Have you read the article and have a question?
    Often our articles are asked the same questions. We collected the answers here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please see the list.

    Also popular now: