V716. Suspicious type conversion: HRESULT -> BOOL (BOOL -> HRESULT).
Анализатор обнаружил в коде ситуацию, при которой явным или неявным образом производится преобразование типа bool или BOOL к HRESULT или наоборот. В то время, как такая операция вполне допустима с точки зрения языка C++, она не имеет практического смысла. Тип HRESULT предназначен для хранения статуса, имеет достаточно сложный формат и не имеет ничего общего с типом bool или BOOL.
Можно привести следующий пример из реального приложения:
BOOL WINAPI DXUT_Dynamic_D3D10StateBlockMaskGetSetting(....)
{
if( DXUT_EnsureD3D10APIs() &&
s_DynamicD3D10StateBlockMaskGetSetting != NULL )
....
else
return E_FAIL;
}
Опасность заключается в том, что тип HRESULT представляет собой, на самом деле, тип 'long', а тип BOOL – это ни что иное как 'int'. Эти типы легко преобразуются друг в друга, и компилятор не видит ничего подозрительного в приведённом выше коде.
Однако с точки зрения программиста эти типы означают совершенно разное. В то время как BOOL обозначает логическую переменную, тип HRESULT устроен достаточно сложно и должен сигнализировать о том, прошла ли операция успешно, какой результат был возвращён после выполнения операции, в случае ошибки – где произошла ошибка, обстоятельства этой ошибки и так далее.
О типе HRESULT. Первый бит слева (то есть самый старший бит) хранит успешность операции: в случае, если операция прошла успешно, первый бит устанавливается в ноль, иначе – в единицу. Дальнейшие четыре бита характеризуют вид ошибки. Одиннадцать бит далее характеризуют модуль, в котором произошла исключительная ситуация. Последние, самые младшие шестнадцать бит характеризуют статус выполнения операции: в случае ошибки он может указывать на код ошибки, в случае успешного выполнения – статус успешного выполнения. Таким образом, неотрицательные значения обычно сигнализируют об успешном выполнении операции. При этом часто применяется макро константа 'S_OK', равная 0.
Более подробное описание HRESULT можно увидеть в статье на сайте MSDN. А на этой странице список наиболее часто применяемых значений.
Тип BOOL для индикации значения "ложь" должен быть равен нулю, в противном случае он указывает на значение "истина". Иными словами, эти два типа крайне похожи друг на друга в плане типов и их приведение друг к другу не влечёт за собой с точки зрения языка ничего страшного, однако операция приведения лишена смысла. Ведь по первоначальному замыслу тип HRESULT хранит в себе не только информацию об успехе или неудаче (и код ошибки в случае неудачи), но и может сохранять в себе некоторую дополнительную информацию в случае успешного вызова. Особенно подпортить жизнь может значение S_FALSE, равное 0x1. То, что ненулевые значения возвращаются в случае успешного вызова крайне редко, может стать причиной мучительных поисков ошибок, проявляющихся очень иногда и совсем изредка. Зачастую можно встретить конструкцию вида:
HRESULT result = someWinApiFunction(....);
if (!result)
{
// This is an error!
}
Такой код абсолютно ошибочен, поскольку проверка на ошибку сработает, если функция вернёт 0 в случае успеха. При этом код обработки ошибки не сработает, когда функция просигнализирует о проблеме, вернув отрицательное число. При этом подобные неявные преобразования между целочисленным и логическим типом могут происходить в более сложных выражениях, где ошибку сложно заметить невооруженным глазом.
Для контроля возвращаемого значения типа HRESULT настоятельно предлагаем воспользоваться макросами SUCCEEDED и FAILED.
HRESULT someFunction(int x);
....
BOOL failure = FAILED(someFunction(q));
В остальных случаях рефакторинг не так уж и прост, как кажется, и требует по крайней мере вдумчивого чтения и анализа кода.
Ещё раз о главном. Помните, что:
- FALSE == 0
- TRUE == 1
- S_OK == 0
- S_FALSE == 1
- E_FAIL == 0x80004005
- и так далее.
Никогда не смешивайте HRESULT и BOOL. Смешивание этих типов является серьёзной ошибкой в логике работы программы. Для проверки значений типа HRESULT используйте специальные макросы.
Для поиска ситуаций, когда в переменную типа HRESULT помещается true или false, существует родственная диагностика V543.
Данная диагностика классифицируется как:
|
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V716. |