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

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

Далее мы процитируем статью с сайта 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-строку, произойдёт сбой в работе этой функции.

Примечание. Анализатор не может точно предсказать, есть в коде настоящая ошибка или нет. Если неправильная 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` не помогает, а наоборот — теряет информацию о типе и ухудшает ситуацию.

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

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

- MSDN. BSTR.
- StackOverfow. Static code analysis for detecting passing a wchar_t* to BSTR.
- StackOverfow. BSTR to std::string (std::wstring) and vice versa.
- Robert Pittenger. Guide to BSTR and CString Conversions.

Данная диагностика классифицируется как:

CWE-704

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