V745. A 'wchar_t *' type string is incorrectly converted to 'BSTR' type string.


Анализатор обнаружил, что со строкой типа "wchar_t *" начинают работать как со строкой типа BSTR. Это очень подозрительно, и код, скорее всего, содержит ошибку. Чтобы лучше понять, в чем заключается опасность, сначала вспомним, что такое BSTR.

На самом деле, мы процитируем статью с сайта MSDN. Читать MSDN документацию не любят, но это нужно сделать. Важно понять, в чем заключается беда, так как предупреждение V745 часто сигнализирует о серьезных ошибках.

typedef wchar_t OLECHAR;
typedef OLECHAR * BSTR;

BSTR (basic string или binary string) – это строковый тип данных, который используется в COM, Automation и Interop функциях. Тип BSTR следует использовать во всех интерфейсах. Представление BSTR:

  • Префикс длины. Целое число размером 4 байта, которое отображает длину следующей за ним строки в байтах. Префикс длины указывается непосредственно перед первым символом строки и не учитывает символ-ограничитель.
  • Строка данных. Строка символов в кодировке Unicode. Может содержать множественные вложенные нулевые символы.
  • Ограничитель. Два нулевых символа.

Тип BSTR является указателем, который указывает на первый символ строки, а не на префикс длины.

Память для BSTR-строк выделяется с помощью функций выделения памяти COM, поэтому они могут возвращаться методами без необходимости контроля над выделением памяти.

Представленный ниже код является неправильным:

BSTR MyBstr = L"I am a happy BSTR";

Данный пример собирается (компилируется и линкуется) успешно, но не будет работать должным образом, поскольку у строки отсутствует префикс длины. Если проверить расположение в памяти данной переменной с помощью отладчика, он покажет отсутствие префикса длины размером 4 байта перед началом строки данных.

Правильный вариант кода должен выглядеть так:

BSTR MyBstr = SysAllocString(L"I am a happy BSTR");

Теперь отладчик покажет наличие префикса длины, который равен значению 34. Оно соответствует 17 символам, которые приводится к wide-character строке с помощью строкового модификатора "L". Отладчик также покажет двухбайтовый символ-ограничитель (0x0000) в конце строки.

Если передать простую Unicode-строку в качестве аргумента функции COM, которая ожидает BSTR-строку, произойдет сбой в работе этой функции.

Надеюсь, процитированного фрагмента MSDN достаточно, чтобы понять, почему следует разделять BSTR и простые строки типа "wchar_t *".

Также надо понимать, что анализатор не может точно предсказать, есть в коде настоящая ошибка или нет. Если неправильная BSTR строка передается куда-то вовне, то произойдёт сбой. Если же BSTR строка превращается обратно в "wchar_t *", то всё хорошо. Имеется в виду код вида:

wchar_t *wstr = Foo();
BSTR tmp = wstr;
wchar_t *wstr2 = tmp;

Да, здесь нет настоящей ошибки. Но это "код с запахом". И его следует поправить. Так он вызовет меньше недоумения у программиста, сопровождающего код, и анализатор не будет выдавать предупреждение. Следует использовать правильные типы данных:

wchar_t *wstr = Foo();
wchar_t *tmp = wstr;
wchar_t *wstr2 = tmp;

Рекомендуем также ознакомиться со ссылками, приведёнными в конце статьи. Они помогут разобраться с BSTR-строками и тем, как их можно конвертировать в строки других типов.

Рассмотрим ещё один пример:

wchar_t *wcharStr = L"123";
wchar_t *foo = L"12345";
int n = SysReAllocString(&wcharStr, foo);

Описание функции SysReAllocString:

INT SysReAllocString(BSTR *pbstr, const OLECHAR *psz);

Выделяет новую BSTR и копирует в нее заданную строку, затем освобождает BSTR, на которую указывает pbstr, и помещает по этому адресу указатель на новую BSTR.

Как видите, функция ожидает в качестве первого аргумента указатель на переменную, содержащую адрес строки в формате BSTR. Но вместо этого ей передают указатель на обыкновенную строку. Так как тип "wchar_t **" с точки зрения компилятора то же самое, что и "BSTR *", то этот код успешно компилируется. Но на практике он не имеет смысла и приведёт к ошибке на этапе исполнения.

Правильный вариант кода:

BSTR wcharStr = SysAllocString(L"123");
wchar_t *foo = L"12345";
int n = SysReAllocString(&wcharStr, foo);

Дополнительно рассмотрим ситуацию, когда используется ключевое слово 'auto'. Анализатор выдаёт предупреждение на следующий безобидный код:

auto bstr = ::SysAllocStringByteLen(foo, 3);
ATL::CComBSTR value;
value.Attach(bstr);  // Warning: V745

Да, это ложное срабатывание, но формально анализатор прав, выдавая предупреждение. Переменная 'bstr' имеет тип 'wchar_t *'. Компилятор языка C++ при выведении типа для auto-переменной не учитывает, что функция возвращает значение типа 'BSTR'. При выведении 'auto', тип 'BSTR' - это просто синоним 'whar_t *'. Получается, что написанный код эквивалентен:

wchar_t *bstr = ::SysAllocStringByteLen(foo, 3);
ATL::CComBSTR value;
value.Attach(bstr);

Поэтому анализатор PVS-Studio и выдаёт предупреждение, так как нехорошо хранить указатель на 'BSTR' строку в обыкновенном 'wchar_t *' указателе. Чтобы устранить предупреждение, следует отказаться в данном месте от 'auto' и написать тип явно:

BSTR *bstr = ::SysAllocStringByteLen(foo, 3);

ATL::CComBSTR value;

value.Attach(bstr);

Это интересный случай, когда оператор 'auto' не помогает, а наоборот, теряет информацию о типе и ухудшает ситуацию.

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

Дополнительные ссылки:

Согласно Common Weakness Enumeration, потенциальные ошибки, найденные с помощью этой диагностики, классифицируются как CWE-704.

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V745.


Найденные ошибки

Проверено проектов
364
Собрано ошибок
13 504

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;