LibreOffice: страшный сон бухгалтера


    LibreOffice — мощный офисный пакет, который бесплатен для частного, образовательного и коммерческого использования. Его разработчики делают замечательный продукт, который во многих сферах используется в качестве альтернативы Microsoft Office. Команде PVS-Studio всегда интересно взглянуть на код таких известных проектов и попробовать найти в них ошибки. В этот раз сделать это было легко. Проект содержит много ошибок, которые могут привести к серьёзным проблемам. В статье будут рассмотрены некоторые интересные дефекты, найденные в коде.

    Введение


    LibreOffice — очень крупный C++ проект. Поддерживать проект такого объёма — сложная задача для команды разработчиков. И, к сожалению, складывается впечатление, что качеству кода LibreOffice не удаётся уделять достаточного внимания.

    С одной стороны, проект просто огромный, не каждый инструмент статического или динамического анализа осилит анализ 13к файлов исходного кода. Столько файлов участвует в сборке офисного пакета вместе со сторонними библиотеками. В основном репозитории LibreOffice хранится около 8к файлов исходного кода. Такой объём кода создаёт проблемы не только разработчикам:


    С другой стороны, у проекта много пользователей и требуется найти и исправить как можно больше ошибок. Каждая ошибка может причинять боль сотням и тысячам пользователей. Поэтому большой размер кодовой базы не должен становиться поводом отказаться от использования тех или иных инструментов, способных обнаружить ошибки. Думаю, читатель уже догадался, что речь идёт о статических анализаторах кода :).

    Да, использование статических анализаторов не гарантирует отсутствия ошибок в проекте. Однако такие инструменты, как PVS-Studio, способны найти большое количество ошибок ещё на этапе разработки и тем самым уменьшить объём работ, связанных с отладкой и поддержкой проекта.

    Давайте посмотрим, что можно найти интересного в исходных кодах LibreOffice, если взять статический анализатор кода PVS-Studio. Возможности запуска анализатора обширны: Windows, Linux, macOS. Для написания этого обзора использовался отчёт PVS-Studio, созданный при анализе проекта на Windows.

    Изменения с последней проверки в 2015 году




    В марте 2015 года был выполнен первый анализ LibreOffice ("Проверка проекта LibreOffice") с помощью PVS-Studio. С тех пор офисный пакет сильно развился как продукт, но внутри всё также содержит множество ошибок. А некоторые паттерны ошибок вообще не поменялись с тех пор. Вот, например, ошибка из первой статьи:

    V656 Variables 'aVRP', 'aVPN' are initialized through the call to the same function. It's probably an error or un-optimized code. Consider inspecting the 'rSceneCamera.GetVRP()' expression. Check lines: 177, 178. viewcontactofe3dscene.cxx 178

    void ViewContactOfE3dScene::createViewInformation3D(....)
    {
      ....
      const basegfx::B3DPoint aVRP(rSceneCamera.GetVRP());
      const basegfx::B3DVector aVPN(rSceneCamera.GetVRP());  // <=
      const basegfx::B3DVector aVUV(rSceneCamera.GetVUV());
      ....
    }

    Эта ошибка исправлена, но вот что нашлось в самой последней версии кода:

    V656 Variables 'aSdvURL', 'aStrURL' are initialized through the call to the same function. It's probably an error or un-optimized code. Consider inspecting the 'pThm->GetSdvURL()' expression. Check lines: 658, 659. gallery1.cxx 659

    const INetURLObject&  GetThmURL() const { return aThmURL; }
    const INetURLObject&  GetSdgURL() const { return aSdgURL; }
    const INetURLObject&  GetSdvURL() const { return aSdvURL; }
    const INetURLObject&  GetStrURL() const { return aStrURL; }
    
    bool Gallery::RemoveTheme( const OUString& rThemeName )
    {
      ....
      INetURLObject   aThmURL( pThm->GetThmURL() );
      INetURLObject   aSdgURL( pThm->GetSdgURL() );
      INetURLObject   aSdvURL( pThm->GetSdvURL() );
      INetURLObject   aStrURL( pThm->GetSdvURL() ); // <=
      ....
    }

    Как вы могли заметить, едва различимые составные имена функций до сих пор являются источником ошибок.

    Ещё один интересный пример из старого кода:

    V656 Variables 'nDragW', 'nDragH' are initialized through the call to the same function. It's probably an error or un-optimized code. Consider inspecting the 'rMSettings.GetStartDragWidth()' expression. Check lines: 471, 472. winproc.cxx 472

    class VCL_DLLPUBLIC MouseSettings
    {
      ....
      long GetStartDragWidth() const;
      long GetStartDragHeight() const;
      ....
    }
    
    bool ImplHandleMouseEvent( .... )
    {
      ....
      long nDragW  = rMSettings.GetStartDragWidth();
      long nDragH  = rMSettings.GetStartDragWidth();
      ....
    }

    Этот фрагмент кода действительно содержал ошибку, которая сейчас исправлена. Но ошибок в коде меньше не становится… Сейчас выявлена похожая ситуация:

    V656 Variables 'defaultZoomX', 'defaultZoomY' are initialized through the call to the same function. It's probably an error or un-optimized code. Consider inspecting the 'pViewData->GetZoomX()' expression. Check lines: 5673, 5674. gridwin.cxx 5674

    OString ScGridWindow::getCellCursor(....) const
    {
      ....
    
      SCCOL nX = pViewData->GetCurX();
      SCROW nY = pViewData->GetCurY();
    
      Fraction defaultZoomX = pViewData->GetZoomX();
      Fraction defaultZoomY = pViewData->GetZoomX(); // <=
      ....
    }

    Ошибки вносятся в код буквально по аналогии.

    Не дай себя обмануть




    V765 A compound assignment expression 'x -= x — ...' is suspicious. Consider inspecting it for a possible error. swdtflvr.cxx 3509

    bool SwTransferable::PrivateDrop(...)
    {
      ....
      if ( rSrcSh.IsSelFrameMode() )
      {
        //Hack: fool the special treatment
        aSttPt -= aSttPt - rSrcSh.GetObjRect().Pos();
      }
      ....
    }

    Вот такой вот интересный «Hack» был найден с помощью диагностики V765. Если упростить строку кода с комментарием, то можно получить неожиданный результат:

    1-й шаг:

    aSttPt = aSttPt - (aSttPt - rSrcSh.GetObjRect().Pos());

    2-й шаг:

    aSttPt = aSttPt - aSttPt + rSrcSh.GetObjRect().Pos();

    3-й шаг

    aSttPt = rSrcSh.GetObjRect().Pos();

    И в чём тогда заключается Hack?

    Ещё один пример на эту тему:

    V567 The modification of the 'nCount' variable is unsequenced relative to another operation on the same variable. This may lead to undefined behavior. stgio.cxx 214

    FatError EasyFat::Mark(....)
    {
      if( nCount > 0 )
      {
        --nCount /= GetPageSize();
        nCount++;
      }
      ....
    }

    Выполнение кода в таких ситуациях может зависеть от компилятора и стандарта языка. Почему бы не переписать этот фрагмент кода проще, понятнее и надёжнее?

    Как не надо использовать массивы и векторы




    По какой-то причине кто-то понаделал множество однотипных ошибок при работе с массивами и векторами. Давайте разберём эти примеры.

    V557 Array overrun is possible. The 'nPageNum' index is pointing beyond array bound. pptx-epptooxml.cxx 1168

    void PowerPointExport::ImplWriteNotes(sal_uInt32 nPageNum)
    {
      ....
      // add slide implicit relation to notes
      if (mpSlidesFSArray.size() >= nPageNum)
          addRelation(mpSlidesFSArray[ nPageNum ]->getOutputStream(),
                      oox::getRelationship(Relationship::NOTESSLIDE),
                      OUStringBuffer()
                      .append("../notesSlides/notesSlide")
                      .append(static_cast<sal_Int32>(nPageNum) + 1)
                      .append(".xml")
                      .makeStringAndClear());
      ....
    }

    Последним валидным индексом должно являться значение, равное size() — 1. Но в этом фрагменте кода допустили ситуацию, когда индекс nPageNum может иметь значение mpSlidesFSArray.size(), из-за чего происходит выход за пределы массива и работа с элементом, состоящим из «мусора».

    V557 Array overrun is possible. The 'mnSelectedMenu' index is pointing beyond array bound. checklistmenu.cxx 826

    void ScMenuFloatingWindow::ensureSubMenuNotVisible()
    {
      if (mnSelectedMenu <= maMenuItems.size() &&
          maMenuItems[mnSelectedMenu].mpSubMenuWin &&
          maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
      {
          maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
      }
    
      EndPopupMode();
    }

    Интересно, что в этом фрагменте кода написали проверку индекса более понятно, но при этом допустили такую же ошибку.

    V557 Array overrun is possible. The 'nXFIndex' index is pointing beyond array bound. xestyle.cxx 2613

    sal_Int32 XclExpXFBuffer::GetXmlStyleIndex( sal_uInt32 nXFIndex ) const
    {
        OSL_ENSURE( nXFIndex < maStyleIndexes.size(), "...." );
        if( nXFIndex > maStyleIndexes.size() )
            return 0;   // should be caught/debugged via above assert;
        return maStyleIndexes[ nXFIndex ];
    }

    А эта ошибка вдвойне интереснее! В отладочном макросе написали правильную проверку индекса, а в другом месте снова сделали ошибку, допустив выход за пределы массива.

    Теперь рассмотрим ошибку иного рода, не связанную с индексами.

    V554 Incorrect use of shared_ptr. The memory allocated with 'new []' will be cleaned using 'delete'. dx_vcltools.cxx 158

    struct RawRGBABitmap
    {
      sal_Int32                     mnWidth;
      sal_Int32                     mnHeight;
      std::shared_ptr< sal_uInt8 >  mpBitmapData;
    };
    
    RawRGBABitmap bitmapFromVCLBitmapEx( const ::BitmapEx& rBmpEx )
    {
      ....
      // convert transparent bitmap to 32bit RGBA
      // ========================================
    
      const ::Size aBmpSize( rBmpEx.GetSizePixel() );
    
      RawRGBABitmap aBmpData;
      aBmpData.mnWidth     = aBmpSize.Width();
      aBmpData.mnHeight    = aBmpSize.Height();
      aBmpData.mpBitmapData.reset( new sal_uInt8[ 4*aBmpData.mnWidth
                                                   *aBmpData.mnHeight ] );
      ....
    }

    Этот фрагмент кода содержит ошибку, приводящую к неопределённому поведению программы. Дело в том, что память выделяется и освобождается разными способами. Для правильного освобождения памяти необходимо было объявить поле класса таким образом:

    std::shared_ptr< sal_uInt8[] > mpBitmapData;

    Как дважды ошибиться в макросах




    V568 It's odd that the argument of sizeof() operator is the 'bTextFrame? aProps: aShapeProps' expression. wpscontext.cxx 134

    oox::core::ContextHandlerRef WpsContext::onCreateContext(....)
    {
      ....
      OUString aProps[] = { .... };
      OUString aShapeProps[] = { .... };
      for (std::size_t i = 0;
           i < SAL_N_ELEMENTS(bTextFrame ? aProps : aShapeProps);                //1
           ++i)
        if (oInsets[i])
          xPropertySet->setPropertyValue((bTextFrame ? aProps : aShapeProps)[i], //2
                                         uno::makeAny(*oInsets[i]));
      ....
    }

    К сожалению для многих разработчиков, аргументы макросов ведут себя не как аргументы функций. Игнорирование этого факта часто приводит к ошибкам. В случаях #1 и #2 используется почти одинаковая конструкция с использованием тернарного оператора. Но в первом случае — макрос, во втором — функция. Однако это только вершина проблемы.

    В случае #1 анализатор на самом деле обнаружил следующий код с ошибкой:

    for (std::size_t i = 0;
         i < (sizeof (bTextFrame ? aProps : aShapeProps) /
              sizeof ((bTextFrame ? aProps : aShapeProps)[0]));
         ++i)

    Это наш цикл с макросом SAL_N_ELEMENTS. Оператор sizeof не вычисляет выражение в тернарном операторе. В данном случае выполняется арифметика с размером указателей, результатом которой являются значения, далёкие от реального размера указанных массивов. На вычисление неправильных значений дополнительно влияет и разрядность приложения.

    Но потом оказалось, что существует 2 макроса SAL_N_ELEMENTS! Т.е. препроцессор раскрыл не тот макрос, как же это могло произойти? Нам поможет определение макроса и комментарии разработчиков:

    #ifndef SAL_N_ELEMENTS
    #    if defined(__cplusplus) &&
            ( defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L )
            /*
             * Magic template to calculate at compile time the number of elements
             * in an array. Enforcing that the argument must be a array and not
             * a pointer, e.g.
             *  char *pFoo="foo";
             *  SAL_N_ELEMENTS(pFoo);
             * fails while
             *  SAL_N_ELEMENTS("foo");
             * or
             *  char aFoo[]="foo";
             *  SAL_N_ELEMENTS(aFoo);
             * pass
             *
             * Unfortunately if arr is an array of an anonymous class then we need
             * C++0x, i.e. see
             * http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#757
             */
             template <typename T, size_t S> char (&sal_n_array_size( T(&)[S] ))[S];
    #        define SAL_N_ELEMENTS(arr)     (sizeof(sal_n_array_size(arr)))
    #    else
    #        define SAL_N_ELEMENTS(arr)     (sizeof (arr) / sizeof ((arr)[0]))
    #    endif
    #endif

    Другая версия макроса содержит безопасную шаблонную функцию, но что-то пошло не так:

    1. Безопасный макрос не включился в код;
    2. Другим макросом всё равно невозможно пользоваться, т.к. успешное инстанцирование шаблонной функции выполняется только если в тернарный оператор передать массивы одинакового размера. А в этом случае использование такого макроса теряет смысл.

    Опечаточки и copy-paste




    V1013 Suspicious subexpression f1.Pitch == f2.CharSet in a sequence of similar comparisons. xmldlg_export.cxx 1251

    inline bool equalFont( Style const & style1, Style const & style2 )
    {
      awt::FontDescriptor const & f1 = style1._descr;
      awt::FontDescriptor const & f2 = style2._descr;
      return (
          f1.Name == f2.Name &&
          f1.Height == f2.Height &&
          f1.Width == f2.Width &&
          f1.StyleName == f2.StyleName &&
          f1.Family == f2.Family &&
          f1.CharSet == f2.CharSet &&    // <=
          f1.Pitch == f2.CharSet &&      // <=
          f1.CharacterWidth == f2.CharacterWidth &&
          f1.Weight == f2.Weight &&
          f1.Slant == f2.Slant &&
          f1.Underline == f2.Underline &&
          f1.Strikeout == f2.Strikeout &&
          f1.Orientation == f2.Orientation &&
          bool(f1.Kerning) == bool(f2.Kerning) &&
          bool(f1.WordLineMode) == bool(f2.WordLineMode) &&
          f1.Type == f2.Type &&
          style1._fontRelief == style2._fontRelief &&
          style1._fontEmphasisMark == style2._fontEmphasisMark
          );
    }

    Ошибка является достойным кандидатом для пополнения статьи "Зло живёт в функциях сравнения", если мы когда-нибудь решим её обновить или расширить. Думаю, вероятность найти такую ошибку (пропуск f2.Pitch) самостоятельно крайне мала. А вы как считаете?

    V501 There are identical sub-expressions 'mpTable[ocArrayColSep] != mpTable[eOp]' to the left and to the right of the '&&' operator. formulacompiler.cxx 632

    void FormulaCompiler::OpCodeMap::putOpCode(....)
    {
      ....
      case ocSep:
          bPutOp = true;
          bRemoveFromMap = (mpTable[eOp] != ";" &&
                  mpTable[ocArrayColSep] != mpTable[eOp] &&
                  mpTable[ocArrayColSep] != mpTable[eOp]);
      break;
      ....
    }

    Результатом бездумного копирования стал такой фрагмент кода. Возможно, условное выражение просто продублировано лишний раз, но всё равно в коде не место таким неоднозначностям.

    V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 781, 783. mysqlc_databasemetadata.cxx 781

    Reference<XResultSet> SAL_CALL ODatabaseMetaData::getColumns(....)
    {
      ....
      bool bIsCharMax = !xRow->wasNull();
      if (sDataType.equalsIgnoreAsciiCase("year"))
          nColumnSize = sColumnType.copy(6, 1).toInt32();
      else if (sDataType.equalsIgnoreAsciiCase("date"))       // <=
          nColumnSize = 10;
      else if (sDataType.equalsIgnoreAsciiCase("date"))       // <=
          nColumnSize = 8;
      else if (sDataType.equalsIgnoreAsciiCase("datetime")
               || sDataType.equalsIgnoreAsciiCase("timestamp"))
          nColumnSize = 19;
      else if (!bIsCharMax)
          nColumnSize = xRow->getShort(7);
      else
          nColumnSize = nCharMaxLen;
      ....
    }

    В результате копирования условных выражений, в коде была допущена ошибка, из-за которой значение 8 для переменной nColumnSize никогда не выставляется.

    V523 The 'then' statement is equivalent to the 'else' statement. svdpdf.hxx 146

    /// Transform the rectangle (left, right, top, bottom) by this Matrix.
    template <typename T> void Transform(....)
    {
      ....
      if (top > bottom)
          top = std::max(leftTopY, rightTopY);
      else
          top = std::min(leftTopY, rightTopY);
    
      if (top > bottom)
          bottom = std::max(leftBottomY, rightBottomY);  // <=
      else
          bottom = std::max(leftBottomY, rightBottomY);  // <=
    }

    Тут перепутали функции min() и max(). Наверняка из-за этой опечатки в интерфейсе что-то странно масштабируется.

    Странные циклы




    V533 It is likely that a wrong variable is being incremented inside the 'for' operator. Consider reviewing 'i'. javatypemaker.cxx 602

    void printConstructors(....)
    {
      ....
      for (std::vector<
                       unoidl::SingleInterfaceBasedServiceEntity::Constructor::
                       Parameter >::const_iterator j(i->parameters.begin());
                       j != i->parameters.end(); ++i)
      {
          o << ", ";
          printType(o, options, manager, j->type, false);
          if (j->rest) {
              o << "...";
          }
          o << ' '
            << codemaker::java::translateUnoToJavaIdentifier(
                u2b(j->name), "param");
      }
      ....
    }

    Выражение ++i в цикле выглядит очень подозрительно. Возможно, там должно быть ++j.

    V756 The 'nIndex2' counter is not used inside a nested loop. Consider inspecting usage of 'nIndex' counter. treex.cxx 34

    SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
    {
      OString sXHPRoot;
      for (int nIndex = 1; nIndex != argc; ++nIndex)
      {
        if (std::strcmp(argv[nIndex], "-r") == 0)
        {
          sXHPRoot = OString( argv[nIndex + 1] );
          for( int nIndex2 = nIndex+3; nIndex2 < argc; nIndex2 = nIndex2 + 2 )
          {
            argv[nIndex-3] = argv[nIndex-1];
            argv[nIndex-2] = argv[nIndex];
          }
          argc = argc - 2;
          break;
        }
      }
      common::HandledArgs aArgs;
      if( !common::handleArguments(argc, argv, aArgs) )
      {
        WriteUsage();
        return 1;
      }
      ....
    }

    Есть какая-то ошибка во внутреннем цикле for. Т.к. переменная nIndex не изменяется, происходит перезаписывание одних и тех же двух элементов массива на каждой итерации. Скорее всего, везде вместо nIndex должна была использоваться переменная nIndex2.

    V1008 Consider inspecting the 'for' operator. No more than one iteration of the loop will be performed. diagramhelper.cxx 292

    void DiagramHelper::setStackMode(
        const Reference< XDiagram > & xDiagram,
        StackMode eStackMode
    )
    {
      ....
      sal_Int32 nMax = aChartTypeList.getLength();
      if( nMax >= 1 )
          nMax = 1;
      for( sal_Int32 nT = 0; nT < nMax; ++nT )
      {
        uno::Reference< XChartType > xChartType( aChartTypeList[nT] );
        ....
      }
      ....
    }

    Цикл for намеренно ограничивается до 1 итерации. Непонятно, зачем это сделано именно таким способом.

    V612 An unconditional 'return' within a loop. pormulti.cxx 891

    SwTextAttr const* MergedAttrIterMulti::NextAttr(....)
    {
      ....
      SwpHints const*const pHints(m_pNode->GetpSwpHints());
      if (pHints)
      {
        while (m_CurrentHint < pHints->Count())
        {
          SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
          ++m_CurrentHint;
          rpNode = m_pNode;
          return pHint;
        }
      }
      return nullptr;
      ....
    }

    Пример более простого странного цикла из одной итерации, который лучше переписать на условный оператор.

    Ещё несколько таких мест:

    • V612 An unconditional 'return' within a loop. txtfrm.cxx 144
    • V612 An unconditional 'return' within a loop. txtfrm.cxx 202
    • V612 An unconditional 'return' within a loop. txtfrm.cxx 279

    Странные условия




    V637 Two opposite conditions were encountered. The second condition is always false. Check lines: 281, 285. authfld.cxx 281

    sal_uInt16  SwAuthorityFieldType::GetSequencePos(sal_IntPtr nHandle)
    {
      ....
      SwTOXSortTabBase* pOld = aSortArr[i].get();
      if(*pOld == *pNew)
      {
        //only the first occurrence in the document
        //has to be in the array
        if(*pOld < *pNew)
          pNew.reset();
        else // remove the old content
          aSortArr.erase(aSortArr.begin() + i);
        break;
      }
      ....
    }

    Анализатор обнаружил противоречивые сравнения. Что-то с этим фрагментом кода явно не так.

    Такой же код замечен и в этом месте:

    • V637 Two opposite conditions were encountered. The second condition is always false. Check lines: 1827, 1829. doctxm.cxx 1827

    V590 Consider inspecting this expression. The expression is excessive or contains a misprint. fileurl.cxx 55

    OUString convertToFileUrl(char const * filename, ....)
    {
      ....
      if ((filename[0] == '.') || (filename[0] != SEPARATOR))
      {
        ....
      }
      ....
    }

    Проблема приведённого фрагмента кода заключается в том, что первое условное выражение не влияет на результат всего выражения.

    По мотивам подобных ошибок я даже написал теоретическую статью: "Логические выражения в C/C++. Как ошибаются профессионалы".

    V590 Consider inspecting this expression. The expression is excessive or contains a misprint. unoobj.cxx 1895

    uno::Sequence< beans::PropertyState >
    SwUnoCursorHelper::GetPropertyStates(....)
    {
      ....
      if (((SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION == eCaller)  ||
           (SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT == eCaller)) &&
          pEntry->nWID < FN_UNO_RANGE_BEGIN &&
          pEntry->nWID > FN_UNO_RANGE_END  &&
          pEntry->nWID < RES_CHRATR_BEGIN &&
          pEntry->nWID > RES_TXTATR_END )
      {
          pStates[i] = beans::PropertyState_DEFAULT_VALUE;
      }
      ....
    }

    Сразу не понять, в чём проблема данного условия, поэтому из препроцессированного файла был выписан развёрнутый фрагмент кода:

    if (((SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION == eCaller)  ||
         (SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT == eCaller)) &&
        pEntry->nWID < (20000 + 1600) &&
        pEntry->nWID > ((20000 + 2400) + 199)  &&
        pEntry->nWID < 1 &&
        pEntry->nWID > 63 )
    {
        pStates[i] = beans::PropertyState_DEFAULT_VALUE;
    }

    Получилось так, что ни одно число не входит одновременно в 4 диапазона, заданных в условии числами. Разработчики допустили ошибку.

    V590 Consider inspecting the '* pData <= MAXLEVEL && * pData <= 9' expression. The expression is excessive or contains a misprint. ww8par2.cxx 756

    const sal_uInt8 MAXLEVEL = 10;
    
    void SwWW8ImplReader::Read_ANLevelNo(....)
    {
      ....
      // Range WW:1..9 -> SW:0..8 no bullets / numbering
      if (*pData <= MAXLEVEL && *pData <= 9)
      {
        ....
      }
      else if( *pData == 10 || *pData == 11 )
      {
          // remember type, the rest happens at Sprm 12
          m_xStyles->mnWwNumLevel = *pData;
      }
      ....
    }

    Из-за того, что в первом условии используется константа со значением 10, условие получилось избыточным. Этот фрагмент кода можно переписать следующим образом:

    if (*pData <= 9)
    {
      ....
    }
    else if( *pData == 10 || *pData == 11 )
    {
      ....
    }

    Но, возможно, в представленном коде была другая проблема.

    V757 It is possible that an incorrect variable is compared with nullptr after type conversion using 'dynamic_cast'. Check lines: 2709, 2710. menu.cxx 2709

    void PopupMenu::ClosePopup(Menu* pMenu)
    {
      MenuFloatingWindow* p = dynamic_cast<MenuFloatingWindow*>(ImplGetWindow());
      PopupMenu *pPopup = dynamic_cast<PopupMenu*>(pMenu);
      if (p && pMenu)
        p->KillActivePopup(pPopup);
    }

    Скорее всего, условие содержит ошибку. Необходимо было проверить указатели p и pPopup.

    V668 There is no sense in testing the 'm_pStream' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. zipfile.cxx 408

    ZipFile::ZipFile(const std::wstring &FileName) :
        m_pStream(nullptr),
        m_bShouldFree(true)
    {
        m_pStream = new FileStream(FileName.c_str());
        if (m_pStream && !isZipStream(m_pStream))
        {
            delete m_pStream;
            m_pStream = nullptr;
        }
    }

    Анализатор обнаружил ситуацию, когда значение указателя, возвращаемого оператором new, сравнивается с нулём. Согласно стандарту языка C++, в случае невозможности выделения памяти, оператор new сгенерирует исключение std::bad_alloc. В проекте LibreOffice нашлось всего 45 подобных мест, что очень мало для такого объёма кода. Но всё равно это может приводить к проблемам у пользователей. Разработчикам следует убрать лишние проверки или создавать объекты таким образом:

    m_pStream = new (std::nothrow) FileStream(FileName.c_str());

    V728 An excessive check can be simplified. The '(A && !B) || (!A && B)' expression is equivalent to the 'bool(A) != bool(B)' expression. toolbox2.cxx 1042

    void ToolBox::SetItemImageMirrorMode( sal_uInt16 nItemId, 
                                          bool bMirror )
    {
      ImplToolItems::size_type nPos = GetItemPos( nItemId );
    
      if ( nPos != ITEM_NOTFOUND )
      {
        ImplToolItem* pItem = &mpData->m_aItems[nPos];
    
        if ((pItem->mbMirrorMode && !bMirror) ||   // <=
           (!pItem->mbMirrorMode &&  bMirror))     // <=
        {
          ....
        }
      }
    }

    Очень давно диагностика V728 была расширена до случаев, которые, скорое всего, не являются ошибочными, но усложняют код. А в сложном коде рано или поздно всё равно допускается ошибки.

    Данное условие упрощается до такого:

    if (pItem->mbMirrorMode != bMirror)
    {
      ....
    }

    В проекте встречается около 60 подобных конструкций, некоторые из них очень и очень громоздкие. Авторам проекта лучше ознакомиться с полным отчётом анализатора PVS-Studio.

    Проблемы с безопасностью




    V523 The 'then' statement is equivalent to the 'else' statement. docxattributeoutput.cxx 1571

    void DocxAttributeOutput::DoWritePermissionTagEnd(
      const OUString & permission)
    {
        OUString permissionIdAndName;
    
        if (permission.startsWith("permission-for-group:", &permissionIdAndName))
        {
            const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':');
            const OUString permissionId   = permissionIdAndName.copy(....);
            const OString rId             = OUStringToOString(....).getStr();
    
            m_pSerializer->singleElementNS(XML_w, XML_permEnd,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSEND);
        }
        else
        {
            const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':');
            const OUString permissionId   = permissionIdAndName.copy(....);
            const OString rId             = OUStringToOString(....).getStr();
    
            m_pSerializer->singleElementNS(XML_w, XML_permEnd,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSEND);
        }
    }

    Здесь скопирован очень крупный фрагмент кода. Для функции, которая изменяет некие права, выявленная проблема выглядит очень подозрительно.

    V1001 The 'DL' variable is assigned but is not used by the end of the function. cipher.cxx 811

    static void BF_updateECB(
        CipherContextBF    *ctx,
        rtlCipherDirection  direction,
        const sal_uInt8    *pData,
        sal_uInt8          *pBuffer,
        sal_Size            nLength)
    {
        CipherKeyBF *key;
        sal_uInt32   DL, DR;
    
        key = &(ctx->m_key);
        if (direction == rtl_Cipher_DirectionEncode)
        {
            RTL_CIPHER_NTOHL64(pData, DL, DR, nLength);
    
            BF_encode(key, &DL, &DR);
    
            RTL_CIPHER_HTONL(DL, pBuffer);
            RTL_CIPHER_HTONL(DR, pBuffer);
        }
        else
        {
            RTL_CIPHER_NTOHL(pData, DL);
            RTL_CIPHER_NTOHL(pData, DR);
    
            BF_decode(key, &DL, &DR);
    
            RTL_CIPHER_HTONL64(DL, DR, pBuffer, nLength);
        }
        DL = DR = 0;
    }

    Переменные DL и DR обнуляются простым присваиванием в конце функции и больше не используются. Для компилятора это повод удалить команды для оптимизации.

    Для правильной очистки переменных в Windows приложениях можно переписать код таким образом:

    RtlSecureZeroMemory(&DL, sizeof(DL));
    RtlSecureZeroMemory(&DR, sizeof(DR));

    Но для LibreOffice здесь потребуется кросс-платформенное решение.

    Похожее предупреждение из другой функции:
    • V1001 The 'DL' variable is assigned but is not used by the end of the function. cipher.cxx 860

    V764 Possible incorrect order of arguments passed to 'queryStream' function: 'rUri' and 'rPassword'. tdoc_storage.cxx 271

    css::uno::Reference< css::io::XStream >
            queryStream( const css::uno::Reference<
                            css::embed::XStorage > & xParentStorage,
                         const OUString & rPassword,
                         const OUString & rUri,
                         StorageAccessMode eMode,
                         bool bTruncate  );
    
    uno::Reference< io::XOutputStream >
    StorageElementFactory::createOutputStream( const OUString & rUri,
                                               const OUString & rPassword,
                                               bool bTruncate )
    {
      ....
      uno::Reference< io::XStream > xStream
          = queryStream(
              xParentStorage, rUri, rPassword, READ_WRITE_CREATE, bTruncate );
      ....
    }

    Обратите внимание, что в функции queryStream в списке параметров сначала идёт rPassword, а затем – rUri. В коде нашлось место, где соответствующие аргументы перепутаны местами при вызове этой функции.

    V794 The assignment operator should be protected from the case of 'this == &rToBeCopied'. hommatrixtemplate.hxx 121

    ImplHomMatrixTemplate& operator=(....)
    {
      // complete initialization using copy
      for(sal_uInt16 a(0); a < (RowSize - 1); a++)
      {
        memcpy(&maLine[a], &rToBeCopied.maLine[a], sizeof(....));
      }
      if(rToBeCopied.mpLine)
      {
        mpLine.reset( new ImplMatLine< RowSize >(....) );
      }
      return *this;
    }

    Приведённый случай больше относится к безопасному написанию кода. В операторе копирования нет проверки на присвоение объекта самому себе. Всего в проекте около 30 таких реализаций оператора копирования.

    Ошибки с SysAllocString




    V649 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains function return. This means that the second 'if' statement is senseless. Check lines: 125, 137. acctable.cxx 137

    STDMETHODIMP CAccTable::get_columnDescription(long column, BSTR * description)
    {
        SolarMutexGuard g;
    
        ENTER_PROTECTED_BLOCK
    
        // #CHECK#
        if(description == nullptr)
            return E_INVALIDARG;
    
        // #CHECK XInterface#
        if(!pRXTable.is())
            return E_FAIL;
        ....
        SAFE_SYSFREESTRING(*description);
        *description = SysAllocString(o3tl::toW(ouStr.getStr()));
        if(description==nullptr) // <=
            return E_FAIL;
        return S_OK;
    
        LEAVE_PROTECTED_BLOCK
    }

    Функция SysAllocString() возвращает указатель, который может иметь значение NULL. Что-то странное написал автор этого кода. Указатель на аллоцированную память не проверяется, что может привести к проблемам в работе программы.

    Похожие предупреждения из других функций:

    • V649 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains function return. This means that the second 'if' statement is senseless. Check lines: 344, 356. acctable.cxx 356
    • V649 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains function return. This means that the second 'if' statement is senseless. Check lines: 213, 219. trvlfrm.cxx 219

    V530 The return value of function 'SysAllocString' is required to be utilized. maccessible.cxx 1077

    STDMETHODIMP CMAccessible::put_accValue(....)
    {
      ....
      if(varChild.lVal==CHILDID_SELF)
      {
        SysAllocString(m_pszValue);
        m_pszValue=SysAllocString(szValue);
        return S_OK;
      }
      ....
    }

    Результат одного из вызовов функции SysAllocString() не используется. Разработчикам следует обратить внимание на этот фрагмент кода.

    Разное


    V716 Suspicious type conversion in return statement: returned HRESULT, but function actually returns BOOL. maccessible.cxx 2649

    BOOL
    CMAccessible::get_IAccessibleFromXAccessible(....)
    {
      ENTER_PROTECTED_BLOCK
    
      // #CHECK#
      if(ppIA == nullptr)
      {
          return E_INVALIDARG; // <=
      }
      BOOL isGet = FALSE;
      if(g_pAgent)
          isGet = g_pAgent->GetIAccessibleFromXAccessible(....);
    
      if(isGet)
          return TRUE;
      else
          return FALSE;
    
      LEAVE_PROTECTED_BLOCK
    }

    Одна из ветвей исполнения функции возвращает значение, тип которого не соответствует типу возвращаемого значения функции. Тип HRESULT имеет более сложный формат, чем тип BOOL, и предназначен для хранения статусов операций. Например, значение E_INVALIDARG равно 0x80070057L. Правильно будет написать, например, так:

    return FAILED(E_INVALIDARG);

    Более подробно об этом можно прочесть в документации к диагностике V716.

    Ещё несколько подобных мест:

    • V716 Suspicious type conversion in return statement: returned HRESULT, but function actually returns BOOL. inprocembobj.cxx 1299
    • V716 Suspicious type conversion in return statement: returned HRESULT, but function actually returns BOOL. maccessible.cxx 2660

    V670 The uninitialized class member 'm_aMutex' is used to initialize the 'm_aModifyListeners' member. Remember that members are initialized in the order of their declarations inside a class. fmgridif.cxx 1033

    FmXGridPeer::FmXGridPeer(const Reference< XComponentContext >& _rxContext)
                :m_aModifyListeners(m_aMutex)
                ,m_aUpdateListeners(m_aMutex)
                ,m_aContainerListeners(m_aMutex)
                ,m_aSelectionListeners(m_aMutex)
                ,m_aGridControlListeners(m_aMutex)
                ,m_aMode( getDataModeIdentifier() )
                ,m_nCursorListening(0)
                ,m_bInterceptingDispatch(false)
                ,m_xContext(_rxContext)
    {
        // Create must be called after this constructor
        m_pGridListener.reset( new GridListenerDelegator( this ) );
    }
    
    class  __declspec(dllexport) FmXGridPeer:
        public cppu::ImplInheritanceHelper<....>
    {
        ....
        ::comphelper::OInterfaceContainerHelper2 m_aModifyListeners,
                                                 m_aUpdateListeners,
                                                 m_aContainerListeners,
                                                 m_aSelectionListeners,
                                                 m_aGridControlListeners;
        ....
    protected:
        css::uno::Reference< css::uno::XComponentContext >  m_xContext;
        ::osl::Mutex                                        m_aMutex;
        ....
    };

    Согласно стандарту языка, порядок инициализации членов класса в конструкторе происходит в порядке их объявления в классе. В нашем случае поле m_aMutex будет инициализировано уже после того, как поучаствует в инициализации пяти других полей класса.

    Вот как выглядит конструктор одного из полей класса:

    OInterfaceContainerHelper2( ::osl::Mutex & rMutex );

    Объект мьютекса передаётся по ссылке. В этом случае могут возникать разные проблемы: от обращения к неинициализированной памяти, до последующей потери изменений объекта.

    V763 Parameter 'nNativeNumberMode' is always rewritten in function body before being used. calendar_jewish.cxx 286

    OUString SAL_CALL
    Calendar_jewish::getDisplayString(...., sal_Int16 nNativeNumberMode )
    {
      // make Hebrew number for Jewish calendar
      nNativeNumberMode = NativeNumberMode::NATNUM2;
    
      if (nCalendarDisplayCode == CalendarDisplayCode::SHORT_YEAR) {
        sal_Int32 value = getValue(CalendarFieldIndex::YEAR) % 1000;
        return mxNatNum->getNativeNumberString(...., nNativeNumberMode );
      }
      else
        return Calendar_gregorian::getDisplayString(...., nNativeNumberMode );
    }

    Аргументы функции перезаписывают по разным причинам: борьба с предупреждениями компилятора, обратная совместимость, «костыль» и т.п. Тем не менее, любое из этих решений не является хорошим, а код выглядит запутанным.

    Следует сделать обзор кода этого места и ещё несколько похожих:

    • V763 Parameter 'bExtendedInfo' is always rewritten in function body before being used. graphicfilter2.cxx 442
    • V763 Parameter 'nVerbID' is always rewritten in function body before being used. oleembed.cxx 841
    • V763 Parameter 'pCursor' is always rewritten in function body before being used. edlingu.cxx 843
    • V763 Parameter 'pOutput' is always rewritten in function body before being used. vnew.cxx 181
    • V763 Parameter 'pOutput' is always rewritten in function body before being used. vnew.cxx 256

    Заключение


    Желание проверить код LibreOffice у меня появилось после личного использования продукта. По непонятной причине, при некоторых случайных запусках абсолютно из всех пунктов меню исчезает текст. Остаются только иконки и монотонные полоски. Ошибка, скорее всего, высокоуровневая и, возможно, её нельзя найти с помощью инструментов статического анализа. Тем не менее, анализатор нашёл очень много проблем, не связанных с этим, и я буду рад, если разработчики LibreOffice обратят внимание на статические анализаторы кода и попробуют использовать их для повышения качества и надёжности проекта. Это будет полезно всем.

    Спасибо за внимание. Подписывайтесь на наши каналы и следите за новостями из мира программирования!




    Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. LibreOffice: Accountant's Nightmare

    PVS-Studio

    510,00

    Ищем ошибки в C, C++ и C# на Windows, Linux, macOS

    Поделиться публикацией

    Похожие публикации

    Комментарии 82
      +6
      Желающие поддержать проект, могут помочь с баг-репортами по статье, а также поучаствовать в Coming up on 22 October: First Bug Hunting Session for LibreOffice 6.2.
        +1
        Работы по иправлению ошибок ведутся в задаче Bug 120703. Все выходные Mike Kaganski вносил правки. Видимо. хотят успеть к выпуску LibreOffiece 6.2.
          0
          Пошто Xisco Fauli и Caolán McNamara обидели?
            0
            Ну там много кто делает, и основную работу, как всегда — Caolan.
              +1
              Ещё не все пишут pvs-studio целиком. По pvs там 4 страницы с коммитами найдётся. Неплохо для выходных.
          +7
          То чувство, когда PVS-Studio пишет о найденном баге, а ты его уже исправил. Правда, случайно.
            0
            Ну три десятка багов так не исправишь, конечно. И вообще это не самая надёжная методика контроля качества проекта :D
            +4

            Очень знакомый стиль кода. Я думаю, в либру ушел разработчик с нашего проекта. :). Язык сменил, а идеи остались. К сожалению, статический анализатор тут бессилен, прогер опять точно такие ошибки ещё раз сделает. Поможет только обучение, что б автор кода пересмотрел свой взгляд.

              +1
              Напротив, именно против таких прогеров и помогает статический анализатор. Только его надо запускать не от случая к случаю, а регулярно.
                +2
                Кстати да. Я с тех пор, как начал регулярно запускать cppcheck, стал намного качественнее писать код. Когда статический анализатор из раза в раз ругается на одно и то же, даже до обезьянки рано или поздно дойдёт.
                  +3
                  — Как преуспеть в использовании статического анализа кода?
                  — Просто используйте его регулярно!

                  Это то, что мы всем и всегда говорим.
              +1
              Исправление ошибок это всегда здорово. Но проблема либры в первую очередь не в багах, а в невероятной тормознутости. Впрочем проблемой в наш золотой век это не считается, так что исправлять вряд ли кто будет…
                +1
                Тормознутость в каких операциях и на каком железе? «У меня всё работает» (с), ну или точнее — на моих компьютерах что Libre, что свежий MS Office работают с примерно идентичной хорошей скоростью.
                  0
                  i7 пятилетней давности, 16 Gb, SSD. В первую очередь лаги интерфейса. Можно смириться с притормаживанием при пролистывании большого документа с картинками. Но что окно свойств (ну там character/paragraph во Writer, или линии в Draw) открывается по полторы-две секунды — хочется рвать и метать. Может там какие несовместимости джав-дотнетов иль еще чего — не знаю. Но OO на той же машине выглядит сильно лучше.
                    0

                    Спасибо за информацию! Мои машинки такие же или слабее (вплоть до самых бюджетных AMD) и документы при листании не лагают, но они и сильно проще — максимум 50 страниц, обычно без картинок, но с таблицами. Лагов интерфейса не замечал, понаблюдаю на досуге. А вот баги попадались — раз в несколько версий например отламывают счётчик слов и символов (считает только при открытии документа, но не в процессе редактирования).

                    0
                    Да много их, автозамена текста, особенно с регулярками, на 500к ячеек запросто может заставить совершить хардрезет над либрой. У майков это проявляется меньше, особенно в новейших версиях офиса
                    0
                    Я замечал, что опенофис немного тормознутее майкрософтовского собрата, но меня это не особо беспокоит. Я использую его в первую очередь как просмотрщик ворд/эксель документов, иногда делаю простые таблицы. Прекрасная программа.

                    Ну а если хочется исправить тормоза, это же опен сорс — патчез ар велком.
                      +1
                      > Ну а если хочется исправить тормоза, это же опен сорс — патчез ар велком.

                      Ну ведь лицемерное вранье это. Вроде как да, но по факту мильоны домохозяек (и домохозяинов к коим и себя я отношу) вынуждены употреблять что дают. И ладно бы речь шла об утилите на 1000 строк, которую пилит энтузиаст в обеденный перерыв. Так нет, так себя ведут огромные конторы (не знаю уж какая там модель конкретно у LO, но та же мозилла к примеру), содержащие штат программистов и загребающих деньги на рекламе, поддержке, корпоративных контрактам. А простому смертному пользователю — «у нас опенсорс, ас из, и вообще не бухти и помогай».
                        0
                        Где «лицемерное вранье»? Я лично им пользуюсь не первый год и меня устраивает. Всякие документы с госсайтов отображает нормально. Не надо платить и не надо мучаться с закрытым софтом. Не надо сливать свои данные Гуглу. Не надо принимать сомнительные условия использования от коммерческих компаний. Без всяких условий и обязательств — бери и пользуйся, по моему так выгодная сделка.

                        Справедливости ради, я не круглые сутки работаю с документами, а лишь иногда.

                        Могу впрочем вспомнить баг, когда он не мог показать какую-то презентацию, но эта презентация была создана в другом продукте (в МС офисе) и конечно, тут возможны проблемы — это для любых продуктов справедливо. Точно так же проблемы совместимости есть у разных версий МС офиса и у других коммерческих продуктов.

                        > Вроде как да, но по факту мильоны домохозяек (и домохозяинов к коим и себя я отношу) вынуждены употреблять что дают

                        Что значит вынуждены? С вас взяли обязательство не пользоваться коммерческими продуктами? Вы всегда можете пойти и купить любой другой продукт.
                          +2
                          >Что значит вынуждены? С вас взяли обязательство не пользоваться коммерческими продуктами? Вы всегда можете пойти и купить любой другой продукт.

                          Вот об этом и речь — вечное «в мире свободного софта вам никто и ничего не обязан!». А потом (делают вид, что) удивляются, почему популярно мнение, что свободный софт — не для людей.
                      0
                      Если говорим про то, что либра не устраивает, то пробовали ли вы onlyoffice?
                        0
                        Они odt конвертят в екселевский формат, т.е. интерпретатор у них один.
                        И когда я его потестил на калковской таблице с годик назад всё было печально.
                        При этом ООо точно работало по разному если файл был в формате эксель 97, например (на либре не сравнивал) — тогда количество столбцов обрезалось, например. И формулы работали по другому.
                        Поэтому, без нормального анализа как работало то или иное в соответствующих версиях ПО — нормально отобразить любой файл не получится вообще. ООо, кстати, тоже от версии к версии менял поведение…
                          0
                          Вроде когда то ставил себе, но смысла не увидел. Для себя меня OO устраивает, а LO — корпоративный стандарт от которого так просто не избавишься.
                        0
                        если разработчики LibreOffice обратят внимание на статические анализаторы кода и попробуют использовать их для повышения качества и надёжности проекта

                        Как минимум используются Coverity и Cppcheck.
                          +7
                          Такие комментарии греют душу разработчикам PVS-Studio :D
                            +9
                            Просто мало использовать анализатор, нужно ещё использовать результаты анализа! :)
                              +4
                              К сожалению, мы сталкиваемся с этим. Есть разработчики, считающие, что наличие Coverity Scan помогает, как иконка в автомобиле.
                          +4
                          LO это худшее что случалось в моей жизни. Постоянные подвисания, падения, непонятные баги интерфейса… ладно-бы у меня одного, дык это по всей моей конторе и у родителей. Теперь я хотя-б вижу что это не у меня руки кудрявые. Спасибо вам за проверку.
                            0
                            А работаете под Windows?
                              +1
                              ООо был наааамного хуже (вспоминая падения от того, что операции накапливались в памяти и при работе с большими таблицами в какле, её вдруг переставало хватать в 32битных версиях)
                                +1

                                Такое и в Chrome часто встречалось, когда он был 32 битным.

                                  0
                                  В Хроме не особо часто, его обычно спасало разбиение на кучу независимых процессов, которую он практикует с древних времен. А чтобы один процесс хрома разжирел/протек до лимита в 2 ГБ это редкое исключение.

                                  А вот 32 битный FireFox до недавнего времени(до перехода на мультипроцессность и замены движка) просто обожал долбиться головой в стену истекая кровью страдал от утечек памяти и спотыкания о лимиты 32 битной адресации.
                                +2
                                Пользовался OO начиная со второй версии, и заканчивая активно пользоватся версией LO 4 с хвостиком, далее перешел на латех. Опеноффис и либраоффис это лучшее, что мне встречалось для работы с текстами, не считая латеха. Самое главное отличие от МСО в логичности работы со стилями, а кроме них по сути вообще ничего не нужно.
                                Эпизодически работал с 5й веткой, по моему мнению количество ошибок и глюков возросло. Сейчас вроде бы стало лучше, но я с ним теперь активно не работаю.
                                +1
                                alexkuzko (промазал кнопкой) На предприятии большая часть компьютеров (~30 из 50) под windows XP-10, однако проблемы есть и на убунте/лубунте/элементари/опенсусе, причем и под KDE, и под gnome, и под другими веществами. Работаем сугубо под родным форматом LO, документы созданные в нем, ничего из ворда не копируется.

                                Последний косяк заставивший лично меня снести ЛО — создаем таблицу 20 строк 4 колонки, жмем Ctrl+A и с зажатой клавишей Ctrl щелкаем на заголовок колонки B — в 100% случаев либра лично у меня закрывалась с ошибкой и пыталась восстановить документ. Я сдался и поставил 2010 ворд в wine. Задолбали приколы на ровном месте с софтом из дефолтного репозитория.
                                  +2
                                  не смог повтоить данный косяк
                                    0
                                    Подождите, что значит «таблицу 20 строк 4 колонки»? или речь про Writer?
                                    Впрочем, там баг тоже не повторился.
                                    Fedora 28.
                                    LO 1:6.0.6.2-1.fc28
                                      +3
                                      вам и sawser отвечу одним сообщением т.к. не могу быть столь оперативным из-за кармы: Баг был в calc, файл с заполненными 20 строками в 4 колонки.

                                      Баг повторить на соседних компьютерах не мог, по этому уточнил "… либра лично у меня закрывалась...". Убунта 18.04, либра последняя из официальных реп, баг сентябрьский.

                                      Копать и искать в чем первопричина — простите, не мое, я простой пользователь.
                                      0
                                      Я сдался и поставил 2010 ворд в wine

                                      А можно с этого момента поподробнее? Когда я это пробовал, ворд из под вайна жутко глючил в куче мест.
                                    +4
                                    LO запорол мне файл с методичкой, которую я писал для универа. Запорол так, что теперь во всем майкрософтовском софте она приводит к зависанию через минуту-две. Понятия не имею что там произошло, не помогает даже пересохранение в doc и обратно в docx.
                                    Теперь если при мне кто-то заведет старую песню про то, как в линуксе все хорошо с офисом и какая там замечательная совместимость — буду сразу бить табуреткой по голове.

                                    Слава богу теперь есть альтернатива — облачный офис от майкрософта, можно забыть об этом глючно-кривом поделии как о страшном сне (ну и использовать его только для редактирования неважных документов)

                                    Сейчас сюда набегут линуксоиды с минусами наперевес и криками «у меня все работает», но что это изменит?
                                      –1
                                      docx вполне себе человекочитабельный формат. Если есть большое желание, откройте файл и попробуйте разобраться, что там не так. По моему опыту, что либре-офис, что мс-офис при многочисленных правках и изменениях формата могут генерировать адские вложенные конструкции для простейших вещей.
                                        +7
                                        Вот типично линуксовый подход. Мне как-то совершенно неинтересно, что именно там может быть не так с файлом и я не собираюсь в нем лазать. Мне нужно офисное приложение, которое нормально работает и не портит мне файлы.

                                        Скорее даже с самим файлом там все так — он ведь открывается и отображается, просто потом мс офис почему-то зависает. Вряд ли я на глаз определю проблему.
                                        На самом деле мы методом проб и ошибок (работая через облачной офис, в котором оно не зависало) выяснили что как-то связано с оглавлением. Если его удалить то все работает. А вот если вставить обратно — зависает. Причем даже если удалить и вставить в мс офисе, а не в ло, так что дело даже наверное не в самом оглавлении, а в чем-то еще, с ним связанном.
                                          +4

                                          MS Office не спасет вас от подобных проблем, ибо он точно такое же тормоглюкало, норовящее свалиться в самый неожиданный момент (иногда просто выбешивают падения каждые несколько минут), имеющее серьезные проблемы с обратной совместимостью (постоянно ловлю разного рода проблемы с документами, даже сравнительно простыми, созданными всего лишь в другой версии офиса) и некорректно работающее с "не своими" форматами (ODF в MS Office — те же проблемы, что и с OpenXML в LibreOffice).

                                            +3
                                            Так уж получилось (покупали в разное время), у нас в офисе зоопарк из MS Office: есть 2003 с пакетом совместимости, 2010, 2013, 2016. Так вот, довольно часто бывает ситуация, когда тот или иной офис не открывает или открывает не совсем правильно документы, сделанные в другой версии. При этом с LO никаких проблем, даже иногда приходится в нем пересохранять файлы, например, от 2010 офиса, чтобы они корректно открылись в 2016.
                                              +1
                                              МО портит файлы не хуже, уж поверьте, но все равно предпочтительнее либре офиса если есть деньги и работа в вин-среде
                                                +1
                                                Мне нужно офисное приложение, которое нормально работает и не портит мне файлы.
                                                Сюдя по описанию такое приложение у вас есть. Называется LibreOffice.

                                                Ибо если MS Office падает (независимо от того, что вы в него засовываете) — то это проблема только и исключительно MS Office. Не должен редактор падать — независимо от того, какую чушь вы в него пихаете. Почему-то когда браузер от хитрого HTML падает — его чинят, а к MS Office почему другой подход?

                                                Если же сам LibreOffice ваши документы открывает и не падает — тогда уж точно с ним всё в порядке. Глупо обвинять его в том, что у разработчиков другой программы ручи не из того места растут.
                                              0
                                              Альтернатива — WPS Offce. Только им спасаюсь на Линуксе. С Libre/Open реально не понимал, когда мне доказывали, что они ничуть не хуже MS. Тормозят жестко, при этом поддержка форматов далека от совершенства и вообще.
                                                0
                                                WPS Offce — тоже так себе альтернатива. Кривой спеллчекер (начиная с того, что название словаря русского языка — «Русская Федера» — я не шучу), реально дурацкие шрифты (распечатанный документ реально стыдно показывать), не работающие ctrl-команды в русской раскладке.
                                                Какие есть ещё варианты?
                                                0

                                                LO при первом сохранении в неродном формате спрашивает у пользователя, уверен ли он в своем решении и понимает, что ценная информация может быть потеряна и т.п.

                                                  +2
                                                  Зависает майкрософтовская программа, а виноват опенофис?
                                                  +5
                                                  Я вот так и не понял причем здесь бухгалтер. Если хотели сказать, что либреофис лютое говно, то так бы и написали. Скажу вам по секрету, как бухгалтер программисту, бухгалтерам насрать на либреофис, для них страшный сон это 1С 8.3
                                                    0
                                                    страшный сон это 1С 8.3

                                                    золотые слова!
                                                      0
                                                      Вот я тоже читаю читаю, в заголовке одно в тексте другое.
                                                        0

                                                        Ну почему же, сама по себе платформа вполне ничего себе — модно, стильно, но вот какие там конфигурации накорчевывают сверху — это да.

                                                        0
                                                        Может сам пакет офиса содержит много ошибок, но с помощью Либры вылечил «распухшие» файлы MS Excel, которые стали весить 10-12 Мбайт вместо положенных 20-30 килобайт.
                                                          0
                                                          Скорее всего, в вашем файле экселя кто-то задал форматирование для столбца в явном виде, как результат, эксель сохранил все 1М строк на листе, пусть там и пустота. Лечится в офисе нахождением последней значащей строки, выделением пустых строк до пола и удалением строк.
                                                            0
                                                            Вы имеете ввиду ситуации, когда пустые ячейки содержат какую-то информацию? Ну так это правится за 15 секунд. Нагуглить решение проблемы можно быстрее, чем скачать LO и установить его.
                                                              0
                                                              Хм. У меня бывает просто при открытии документа в LibreOffice Calc все пустые ячейки иногда превращаются в выпадающие списки. Приходится тратить больше 15 секунд, чтобы поудалять это всё.
                                                                +1
                                                                Описанное Вами, несколько иная проблема. В Excel действительно есть проблема, когда вместо сохранения информации о группе ячеек, она сохраняется для каждой и таким образом таблица из 5 строк и 3 столбцов занимает до 10-20мб. Скорость работы такого файла соответствующая. Правится выделением свободного диапазона сочетанием клавиш и очисткой форматирования или элементарным макросом.
                                                                А в LibreOffice помимо выпадающих списков много других багов. Я несколько раз пытался хотя бы сам пересесть на него. Очень тяжко…
                                                            +12
                                                            Верните единорогов вместо унылых мемасиков!
                                                              0
                                                              И чё вам не нравится Libre Office?
                                                              Я вон никак не мог скопировать название заголовка для сохранения текстового файла на «Рабочий стол» (в Windows 10).
                                                              Если копировать из заголовка, что в браузере, файл сохраняется.
                                                              Если копировать прямо из Libre Office, вылезает запрос на NET. Framework.
                                                              Установил все эти NET. Framework -и.
                                                              Один фиг при копировании из заголовка в текстовом редакторе Libre Office, при следующем копировании чего либо в текстовый редактор (Libre Office) пакет намертво зависает, выкинув требование NET. Framework-ов.
                                                              Тады я купил новый ноут, подозревая что железо глючит.
                                                              Но куда там!
                                                              Зато я теперь продвинутый пользователь: я знаю, что в Libre Office с версии 5.0 и до версии 6.1.2, (х32, х64), копировать что либо можно только из браузера.
                                                                0
                                                                У LibreOffice вообще есть ошибки потрясающие — до версии 5.0.4 макрос честно вставлял новые строки и столбцы, более новые версии стали работать с точностью до наоборот (!) вместо строк вставляют столбцы, вместо столбцов строки! Вот как можно так писать… И самое интересное — в OpenOffice — эти самые макросы отрабатывают абсолютно нормально. Ну и о самих разработчиках — на обращения в форумах о такой «странной» ошибке — никакой реакции…
                                                                  –2
                                                                  Самой большой оштбкой дизайна С++ и Питон считаю возможность перегрузки операторов. Просто глядя на строку x = x * 0 невозможно сказать, делает она что-то или нет. Надо лезть в класс, смотреть его предков, причём даже это ни в C++ ни в Питоне не может дать однозначную уверенность, что оператор не перегружен. Потому что в С++ используют препроцессоры, а в Питоне можно поменять метод класса у объекта после его создания. Да и метод класса подменить на ходу тоже можно.
                                                                    +3
                                                                    Угу, зачем читать про контракты и интерфейсы. Лучше писать:
                                                                    x.set(x.mul_to_int(0))
                                                                      +4
                                                                      Кто-то пишет говнокод, а виноват, конечно же, язык.
                                                                        0
                                                                        Виновато то, что ни linter, ни профайлер не могут написать «У вас говнокод, анализировать не буду» и всё ещё считаться хорошей утилитой.
                                                                        Именно поэтому для Java у нас есть программы, которые сами рефакторинг всего проекта за вас сделать могут, а на С++ анализатор, увидев конструкцию типа
                                                                        a = 3;
                                                                        a = 3;
                                                                        a = 3;

                                                                        может только вяло проблеять, что наверное, может быть здесь лишняя операция. Сам я пишу на Питоне и имею набор правил, как писать так, чтобы хоть автокомплит в PyCharm работал. Внешние библиотеки моим правилам не следуют и у половины автокомплит показывает не всё, что нужно.
                                                                          +1

                                                                          На джаве будет так:


                                                                          a.assingInt(3);
                                                                          a.assignInt(3);
                                                                          a.assignInt(3);

                                                                          Чем это лучше для человека или refactor tool?

                                                                        0
                                                                        Согласен, но все познается в сравнении. Это вы на ruby наверное не писали, там вообще понять, что делает код невозможно. А еще например явный `return x` в коде считается плохим стилем и генерирует предупреждения style check.
                                                                        +3
                                                                        Про цикл с одной итерацией. Обратите внимание на оформление заголовка и на тело цикла. Вы увидите что синтаксис написан так, чтобы оптимизатор кода использовал xlat ассемблерные операции для работы с табличными списками в памяти, структура скорее всего выровнена до 64х байт, или кратна этому размеру, чтобы помещаться в границы сегмента кэша данных процессора. Банальная оптимизация. Код пишет большое количество разработчиков, и я рад что некоторые из них читали книги по оптимизации ПО от Intel-press.
                                                                          +1
                                                                          Ох, ЛО, как же я с ним мучился последние два дня, нужно было поработать с документами на новом ноуте, лицензионного офиса небыло, поставил ЛО, открываю документик размером 12 мб, собранный в 2003 офисе, ЛО виснит и умирает, кусок из того документа весом 200 кб легко, пришлось поставить офис в триаильном режиме, пересохранить в odt и только тогда с подвисаниями смог открыть.
                                                                            +1
                                                                            Кстати китайский WPS линуксе такой же по скорости как и нативный офис. Но только он все фишки поддерживает, вроде встроенных документов.
                                                                              0
                                                                              Я иногда использую ОнлиОфис — вполне зрелая штука.
                                                                              В ЛО — только для изучения структуры документа. Он таки намного больше всяких косяков отображает закопаных в структуре документа чем тот же МСО
                                                                              +1
                                                                              Много лет пользуюсь только ло и на убунте и в окнах, делал сложные макросы, всё работало и работает как надо. Ворованным софтом никогда не пользуюсь, а платить за офис от майкрософт не вижу никаких причин, если есть такая хорошая альтернатива.
                                                                                0
                                                                                У меня наоборот. 90 % времени в убунте с либрой, еще 5 — гуглдрайв, но оставшиеся 5% вполне оправдывают легальную лицензию на МО и развернутую виртуалку с вин10 — и даже так МО после либры чувствуется как ц класс после логана
                                                                                0
                                                                                Глюкавость либры — это еще полбеды, она проявляется только в достаточно сложных документах. А вот его интерфейс родом прямиком из 90х на фоне удобнейшего МО — вот это просто жесть.
                                                                                  –1
                                                                                  Тем не менее, анализатор нашёл очень много проблем, не связанных с этим, и я буду рад, если разработчики LibreOffice обратят внимание на статические анализаторы кода и попробуют использовать их для повышения качества и надёжности проекта.
                                                                                  А вы им PVS-Studio предлагали? Бесплатно, разумеется, т.к. "Это будет полезно всем"? Или вы хотите что бы разработчики бесплатного проекта с открытым исходным кодом покупали инструменты для разработки?
                                                                                  0

                                                                                  Самые фундаментальные ошибки либры:


                                                                                  1. Захардкоженная запятая
                                                                                  2. Отсутствие "асинхронности" в ui в 2018 году, а также диаграммы из 2002
                                                                                  3. Желалание быть похожим, подражание — свой визуалбейсик со своими премудростями вместо какого нибудь питона или js в качестве макроязыка
                                                                                    +1
                                                                                    да макросы на питоне это хорошая идея. и что интересно её уже реализовали )
                                                                                      0

                                                                                      Пользуясь случаем, хочу спросить у разработчиков анализатора. Почему в последней сборке PVS под Linux пропали исходники к plog-converter из архива *.tgz?

                                                                                        +1
                                                                                        Исходники plog-converter переехали на Github. Для тех, кто не знает, что такое plog-converter, есть описание утилиты. Она позволяет конвертировать отчеты анализатора в различные форматы.

                                                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                      Самое читаемое