- PVSM.RU - https://www.pvsm.ru -
LibreOffice — мощный офисный пакет, который бесплатен для частного, образовательного и коммерческого использования. Его разработчики делают замечательный продукт, который во многих сферах используется в качестве альтернативы Microsoft Office. Команде PVS-Studio всегда интересно взглянуть на код таких известных проектов и попробовать найти в них ошибки. В этот раз сделать это было легко. Проект содержит много ошибок, которые могут привести к серьёзным проблемам. В статье будут рассмотрены некоторые интересные дефекты, найденные в коде.
LibreOffice [1] — очень крупный C++ проект. Поддерживать проект такого объёма — сложная задача для команды разработчиков. И, к сожалению, складывается впечатление, что качеству кода LibreOffice не удаётся уделять достаточного внимания.
С одной стороны, проект просто огромный, не каждый инструмент статического или динамического анализа осилит анализ 13к файлов исходного кода. Столько файлов участвует в сборке офисного пакета вместе со сторонними библиотеками. В основном репозитории LibreOffice хранится около 8к файлов исходного кода. Такой объём кода создаёт проблемы не только разработчикам:
С другой стороны, у проекта много пользователей и требуется найти и исправить как можно больше ошибок. Каждая ошибка может причинять боль сотням и тысячам пользователей. Поэтому большой размер кодовой базы не должен становиться поводом отказаться от использования тех или иных инструментов, способных обнаружить ошибки. Думаю, читатель уже догадался, что речь идёт о статических анализаторах кода :).
Да, использование статических анализаторов не гарантирует отсутствия ошибок в проекте. Однако такие инструменты, как PVS-Studio, способны найти большое количество ошибок ещё на этапе разработки и тем самым уменьшить объём работ, связанных с отладкой и поддержкой проекта.
Давайте посмотрим, что можно найти интересного в исходных кодах LibreOffice, если взять статический анализатор кода PVS-Studio [2]. Возможности запуска анализатора обширны: Windows, Linux, macOS. Для написания этого обзора использовался отчёт PVS-Studio, созданный при анализе проекта на Windows.
В марте 2015 года был выполнен первый анализ LibreOffice ("Проверка проекта LibreOffice [3]") с помощью PVS-Studio. С тех пор офисный пакет сильно развился как продукт, но внутри всё также содержит множество ошибок. А некоторые паттерны ошибок вообще не поменялись с тех пор. Вот, например, ошибка из первой статьи:
V656 [4] 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 [4] 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 [4] 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 [4] 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 [5] 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 [5]. Если упростить строку кода с комментарием, то можно получить неожиданный результат:
1-й шаг:
aSttPt = aSttPt - (aSttPt - rSrcSh.GetObjRect().Pos());
2-й шаг:
aSttPt = aSttPt - aSttPt + rSrcSh.GetObjRect().Pos();
3-й шаг
aSttPt = rSrcSh.GetObjRect().Pos();
И в чём тогда заключается Hack?
Ещё один пример на эту тему:
V567 [6] 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 [7] 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 [7] 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 [7] 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 [8] 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 [9] 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
Другая версия макроса содержит безопасную шаблонную функцию, но что-то пошло не так:
V1013 [10] 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
);
}
Ошибка является достойным кандидатом для пополнения статьи "Зло живёт в функциях сравнения [11]", если мы когда-нибудь решим её обновить или расширить. Думаю, вероятность найти такую ошибку (пропуск f2.Pitch) самостоятельно крайне мала. А вы как считаете?
V501 [12] 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 [13] 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 [14] 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 [15] 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 [16] 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 [17] 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 [18] 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;
....
}
Пример более простого странного цикла из одной итерации, который лучше переписать на условный оператор.
Ещё несколько таких мест:
V637 [19] 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;
}
....
}
Анализатор обнаружил противоречивые сравнения. Что-то с этим фрагментом кода явно не так.
Такой же код замечен и в этом месте:
V590 [20] 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++. Как ошибаются профессионалы [21]".
V590 [20] 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 [20] 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 [22] 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 [23] 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 [24] 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 [24] была расширена до случаев, которые, скорое всего, не являются ошибочными, но усложняют код. А в сложном коде рано или поздно всё равно допускается ошибки.
Данное условие упрощается до такого:
if (pItem->mbMirrorMode != bMirror)
{
....
}
В проекте встречается около 60 подобных конструкций, некоторые из них очень и очень громоздкие. Авторам проекта лучше ознакомиться с полным отчётом анализатора PVS-Studio.
V523 [14] 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 [25] 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 здесь потребуется кросс-платформенное решение.
Похожее предупреждение из другой функции:
V764 [26] 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 [27] 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 таких реализаций оператора копирования.
V649 [28] 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. Что-то странное написал автор этого кода. Указатель на аллоцированную память не проверяется, что может привести к проблемам в работе программы.
Похожие предупреждения из других функций:
V530 [29] 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 [30] 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.
Ещё несколько подобных мест:
V670 [31] 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 [32] 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 );
}
Аргументы функции перезаписывают по разным причинам: борьба с предупреждениями компилятора, обратная совместимость, «костыль» и т.п. Тем не менее, любое из этих решений не является хорошим, а код выглядит запутанным.
Следует сделать обзор кода этого места и ещё несколько похожих:
Желание проверить код LibreOffice у меня появилось после личного использования продукта. По непонятной причине, при некоторых случайных запусках абсолютно из всех пунктов меню исчезает текст. Остаются только иконки и монотонные полоски. Ошибка, скорее всего, высокоуровневая и, возможно, её нельзя найти с помощью инструментов статического анализа. Тем не менее, анализатор нашёл очень много проблем, не связанных с этим, и я буду рад, если разработчики LibreOffice обратят внимание на статические анализаторы кода и попробуют использовать их для повышения качества и надёжности проекта. Это будет полезно всем.
Спасибо за внимание. Подписывайтесь на наши каналы и следите за новостями из мира программирования!
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. LibreOffice: Accountant's Nightmare [38]
Автор: SvyatoslavMC
Источник [39]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/pvs-studio/296260
Ссылки в тексте:
[1] LibreOffice: https://www.libreoffice.org/
[2] PVS-Studio: https://www.viva64.com/ru/pvs-studio/
[3] Проверка проекта LibreOffice: https://www.viva64.com/ru/b/0308/
[4] V656: https://www.viva64.com/ru/w/v656/
[5] V765: https://www.viva64.com/ru/w/v765/
[6] V567: https://www.viva64.com/ru/w/v567/
[7] V557: https://www.viva64.com/ru/w/v557/
[8] V554: https://www.viva64.com/ru/w/v554/
[9] V568: https://www.viva64.com/ru/w/v568/
[10] V1013: https://www.viva64.com/ru/w/v1013/
[11] Зло живёт в функциях сравнения: https://www.viva64.com/ru/b/0509/
[12] V501: https://www.viva64.com/ru/w/v501/
[13] V517: https://www.viva64.com/ru/w/v517/
[14] V523: https://www.viva64.com/ru/w/v523/
[15] V533: https://www.viva64.com/ru/w/v533/
[16] V756: https://www.viva64.com/ru/w/v756/
[17] V1008: https://www.viva64.com/ru/w/v1008/
[18] V612: https://www.viva64.com/ru/w/v612/
[19] V637: https://www.viva64.com/ru/w/v637/
[20] V590: https://www.viva64.com/ru/w/v590/
[21] Логические выражения в C/C++. Как ошибаются профессионалы: https://www.viva64.com/ru/b/0390/
[22] V757: https://www.viva64.com/ru/w/v757/
[23] V668: https://www.viva64.com/ru/w/v668/
[24] V728: https://www.viva64.com/ru/w/v728/
[25] V1001: https://www.viva64.com/ru/w/v1001/
[26] V764: https://www.viva64.com/ru/w/v764/
[27] V794: https://www.viva64.com/ru/w/v794/
[28] V649: https://www.viva64.com/ru/w/v649/
[29] V530: https://www.viva64.com/ru/w/v530/
[30] V716: https://www.viva64.com/ru/w/v716/
[31] V670: https://www.viva64.com/ru/w/v670/
[32] V763: https://www.viva64.com/ru/w/v763/
[33] @pvsstudio_rus: https://vk.com/pvsstudio_rus
[34] @pvsstudio_rus: https://t.me/pvsstudio_rus
[35] @pvsstudio_rus: https://www.instagram.com/pvsstudio_rus/
[36] @pvsstudio_rus: https://twitter.com/pvsstudio_rus
[37] @PVSStudioTool: https://www.youtube.com/c/PVSStudioTool
[38] Image: https://www.viva64.com/en/b/0586/
[39] Источник: https://habr.com/post/426977/?utm_campaign=426977
Нажмите здесь для печати.