SAL аннотации

В последнее время я немного забросил Win32.Utf8 - на работе завал, так что, приходя домой, делать ничего особенного не хочется. Тем не менее, проект потихоньку движется и, на сегодняшний день, я добрался до разбора SAL аннотаций.

Основной источник информации, которым я пользуюсь - это заголовок sal.h из поставки Visual C++. В нем содержится достаточно подробное описание того, как составляются конструкции вроде __deref_inout_ecount_part_opt. Кроме того, там же определены макросы, преобразующие SAL аннотации в набор директив для статического анализатора PREfast. По ним достаточно просто понять как SAL аннотации «работают на самом деле».

Аналогичный заголовок есть в Platform SDK для Windows 2003 - specstrings.h. В Windows SDK для Vista все еще более запутано.

SAL использует следующую модель для описания аргументов функций и членов структур. Каждый аргумент функции/член структуры условно представляется одним или несколькими уровнями «вложенности»:

Например, для параметра LPSTR* arg 0-ой уровень это сам arg (указатель на указатель), 1-ый - указатель на строку, на которую ссылается arg и 2-ой - это сама строка, вернее буфер, выделенный под строку.

Для каждого из уровней вложенности могут быть заданы следующие параметры:

Не все параметры применимы ко всем уровням. Например, для 0-ого уровня имеют смысл только _in, _out и _opt , поскольку длина «буфера» на нулевом уровне всегда равняется одному элементу. Текущая реализация SAL накладывает ещё больше ограничений. К примеру, она не даёт возможности описывать аргументы типа «массив указателей на массив» и работать задавать параметры уровней глубже 2-ого. Фактически SAL позволяет описать только наиболее распространенные комбинации указателей в параметрах, которые, тем не менее, позволяют описать 99% случаев, встречающихся в реальной жизни.

С учетом всего вышесказанного можно приступать к конструированию SAL аннотации. Для этого используется следующая таблица (из sal.h):

Level Usage Size Output Null-terminated Optional Parameters
- - - - - - -
_deref _in _ecount _full _z _opt (size)
_deref_opt _out _bcount _part _nz (size, length)
_inout

Порядок элементов в таблице совпадает с тем, как элементы должны указываться в аннотации.

Level: выбирает уровни которые определяет данная аннотация:

Usage: указывает, что аргумент должен быть действителен до/после вызова функции.

Size: определяет размер буфера на уровне, выбранном с помощью _deref. Размер указывается в скобках после SAL директивы. _ecount/_bcount выбирает единицу изменения длины буфера: элементы/байты.

Output: позволяет описывать не до конца проинициализированный буфер.

Null-terminated: явно указывает, что данные завершаются нулевым элементом.

Optional: наличие _opt говорит о том, что аргумент функции может быть нулевым. В отличии от _deref_opt, _opt относится к 0-у уровню вложенности. Это позволяет комбинировать _deref_opt и _opt, указывая, что оба указателя могут быть нулевыми.

Parameters: параметры задают длину буфера и длину его инициализированной части.

Итого:

Пример: параметр вида LPSTR* Arg, который используется для получения строки из функции может быть описан следующим образом:

__deref_out_z LPSTR* Arg;

Такой объявление означает, что по завершению функции указатель на который указывает Arg будет проинициализирован указателем на строку, завершающуюся нулём. Обратите внимание, что для того чтобы это работало Arg должен быть действительным ненулевым указателем во время вызова функции, т.е. Arg должен быть описан как _in параметр на нулевом и первом уровнях вложенности, однако никаких явных _in в примере нет. Причина в том, что этот _in неявно подразумевается при указании _deref, иначе аннотация противоречила бы самой себе.

Другие подводные камни, стоящие упоминания, - это константные параметры (подразумевают _in) и правила комбинирования SAL аннотаций параметров функций, типов и членов структур. Но об этом в следующий раз.

comments powered by Disqus