Протокол сервера статического анализа (in russian)¶
Данный документ описывает библиотеку ASP (Analysis Server Protocol) для взаимодействия с сервером статического анализа.
Технические сведения¶
Протокол взаимодействия с сервером статического анализа основан на JSON. Предполагается, что основным транспортным протоколом будет TCP, и библиотека реализует именно его. Базовый порт в текущей реализации: 5500.
Библиотека спроектирована так, чтобы обеспечить минимальное число зависимостей от внешних проектов. Основная зависимость - парсер JSON JSMN.
Инициализация библиотеки¶
Работа с библиотекой начинается с вызова функции @p asp_initialize(). Основное её применение - в инициализации генератора случайных чисел и сокетов для Windows.
Коды ошибок¶
Коды ошибок представлены в перечислении @p asp_error (см. @ref asp_status). Самым тривиальным является код @c ASP_EOK, обозначающий отсутствие ошибок.
Основные концепции¶
Протокол выделяет несколько основных сущностей: адрес (@ref asp_address), соединение (@ref asp_connection), рабочее пространство (@ref asp_workspace), файл (@ref asp_file), а также анализ (@ref asp_analysis) и результат анализа (@ref asp_analysis_result). Вспомогательной сущностью является окружение (@ref asp_env).
Адрес¶
Адрес (@p asp_address) является сущностью, задающей путь до какого-либо объекта или до сервера.
Создание адреса до сервера осуществляется через @p asp_address_ip() с указанием IP-адреса или FQDN сервера и порта. Например:
asp_address *aa = asp_address_ip("127.0.0.1", 5500);
...
asp_address_free(aa);
Создание адреса до файла осуществляется через @p asp_address_local_file() с указанием пути до целевого файла. Например:
asp_address *aa = asp_address_local_file("analyzed.c");
...
asp_address_free(aa);
Как следует из примеров, адрес уничтожается через @p asp_address_free().
Соединение¶
Соединение (@p asp_connection) является общим представлением сессии взаимодействия с сервером. Создается при помощи @p asp_connection_create(). Сразу после вызова этой функции соединение не инициализировано.
Выбор целевого сервера осуществляется через @p asp_connection_connect() с указанием адреса сервера. Следует отметить, что для упрощения рабочего процесса @p asp_connection можно установить параметр @p own_address в @c true, и тогда высвобождение адреса не будет требоваться.
Пример создания соединения:
asp_connection *conn;
conn = asp_connection_create();
if (conn == NULL)
{
/* Ошибка: соединение не было создано */
}
rc = asp_connection_connect(conn,
asp_address_ip("127.0.0.1", 5500),
true);
if (rc != ASP_EOK)
{
/* Ошибка: соединение не было установлено */
}
...
asp_connection_close(conn);
asp_connection_free(conn);
Рабочее пространство¶
Рабочее пространство (@p asp_workspace) включает в себя информацию о среде общего характера, а также список файлов. Создается при помощи @p asp_workspace_create(), уничтожается через @p asp_workspace_free().
Реальное создание соединения происходит при вызове @p asp_connection_add_workspace(), удаление - при вызове @p asp_connection_destroy_workspace(). Если до реального создания соединения в него будут добавлены файлы через @p asp_workspace_add_file(), то они будут автоматически добавлены в соединение.
Пример создания пространства:
asp_workspace *ws;
asp_connection *conn;
/* Подготовка соединения (conn) */
ws = asp_workspace_create();
if (ws == NULL)
{
/* Ошибка создания рабочей среды */
}
rc = asp_connection_add_workspace(conn, ws);
if (rc != ASP_EOK)
{
/* Ошибка добавления рабочей среды на сервер */
}
rc = asp_connection_destroy_workspace(conn, ws);
if (rc != ASP_EOK)
{
/* Ошибка удаления рабочей среды с сервера */
}
asp_workspace_free(ws);
Файл¶
Файл (@p asp_file) отвечает за представление информации об отдельном файле рабочей среды. К ней относится способ получения файла. Файл создается при помощи @p asp_file_create(), уничтожается при помощи @p asp_file_free().
Пример создания файла:
asp_status rc;
asp_connection *conn;
asp_workspace *ws;
asp_file *file;
/* Подготовка соединения (conn) и рабочей среды (ws) */
file = asp_file_create(asp_address_local_file("C:\\analyzed.c"),
true);
if (file == NULL)
{
/* Ошибка создания файла */
}
rc = asp_connection_add_workspace_file(conn, ws, file);
if (rc != ASP_EOK)
{
/* Ошибка добавления файла на сервер */
}
...
asp_file_free(file);
Также файл можно добавить в рабочее пространство заблаговременно при помощи @p asp_workspace_add_file(). В таком случае, весь список файлов будет добавлен вместе с рабочим пространством.
Окружение¶
Окружение (@p asp_env) описывает среду, в которой будет компилироваться или исполняться анализируемая программа. К таковым относятся компилятор, процессор, язык и целевая операционная система. Окружение можно создать через @p asp_env_create() и уничтожить через @p asp_env_free() следующим образом:
asp_env *env;
env = asp_env_create();
...
asp_env_free(env);
Заполнение окружения происходит при помощи специфичных для каждого поля функций, например, @p asp_env_set_compiler(), @p asp_env_set_language(), @p asp_env_set_cpu(), @p @p asp_env_set_runtime_os(). Например (без проверки на ошибки):
rc = asp_env_set_compiler(ASP_COMPILER_RUC);
...
rc = asp_env_set_language(ASP_LANGUAGE_CPP);
...
rc = asp_env_set_cpu(ASP_CPU_MIPS32_LE);
...
rc = asp_env_set_runtime_os(ASP_OS_OSX);
...
Добавить папку с заголовочными файлами можно через @p asp_env_add_include_directory(). Например:
rc = asp_env_add_include_directory(env, asp_address_local_file("C:\\directory"), true);
...
Окружение не может существовать отдельно от среды или конкретного файла. Присоединить окружение к рабочему пространству можно через @p asp_workspace_attach_env(), а к файлу - через @p asp_file_attach_env(). Например (без проверок на ошибки):
asp_workspace *workspace;
asp_file *file;
env = asp_env_create();
rc = asp_file_attach_env(file, env, true);
...
rc = asp_workspace_attach_env(workspace, env, true);
...
Анализ и его результаты¶
Объект анализа @p asp_analysis создается при помощи @p asp_analysis_create(). Чтобы указать, какие виды анализа необходимо выполнить, важно создать объекты @p asp_analysis_job и присоединить их к анализу. Вызов анализа на сервере осуществляется через @p asp_connection_start_analysis() с указанием соединения, пространства, а также созданного объекта анализа. В случае получения @p ASP_EOK, можно считать, что анализ добавлен в очередь на сервере. Уничтожение объекта анализа осуществляется через @p asp_analysis_result_free().
Результат анализа хранится в отдельном объекте @p asp_analysis_result, который создается через @p asp_analysis_result_create(). Наполнение этого объекта осуществляется через @p asp_connection_get_analysis_result(), при этом в случае, если анализ ещё не завершился, возвращается ошибка. Уничтожение объекта требуется только в случае получения положительного результата анализа и осуществляется через @p asp_analysis_result_free().
Ожидание результата возможно через вызов @p asp_connection_wait_analysis(). При этом следует понимать, что уведомление придет только на сокет, первоначально создавший объект анализа. Если доступа к этому сокету нет, то результат можно получить исключительно через @p asp_connection_get_analysis_result(). Ожидание является блокирующей операцией, следовательно, разумно поместить её в отдельный поток.
Пример осуществления анализа и получения результатов:
asp_status rc;
asp_connection *conn;
asp_workspace *ws;
asp_analysis *aa;
asp_analysis_job *job;
asp_analysis_result *aar;
/* Подготовка conn, ws и добавление файлов */
aa = asp_analysis_create();
if (aa == NULL)
{
/* Ошибка создания анализа */
}
job = asp_analysis_job_create();
if (job == NULL)
{
/* Ошибка создания задачи */
}
asp_analysis_job_set_kind(job, ASP_ANALYSIS_JOB_KIND_DEFECTS);
rc = asp_analysis_add_job(aa, job, true);
if (rc != ASP_EOK)
{
/* Ошибка добавления задачи в объект */
}
rc = asp_connection_start_analysis(conn, ws, aa);
if (rc != ASP_EOK)
{
/* Ошибка вызова анализа на сервере */
}
aar = asp_analysis_result_create();
if (aar == NULL)
{
/* Ошибка создания объекта для хранения результата анализа */
}
rc = asp_connection_wait_analysis(conn, aa);
if (rc != ASP_EOK)
{
/* Результат не был получен */
}
rc = asp_connection_get_analysis_result(conn, ws, aa, aar);
if (rc != ASP_EOK)
{
/* Анализ ещё не завершился */
}
rc = asp_connection_destroy_analysis(conn, ws, aa);
if (rc != ASP_EOK)
{
/* Ошибка уничтожения анализа на сервере */
}
asp_analysis_result_free(aar);
asp_analysis_free(aa);
Результат анализа содержит в себе полный текст результата в формате SARIF (JSON), но также доступен и в формате связанного списка. Доступ к первому отчету осуществляется через @p asp_analysis_result_get_report(), доступ к следующему отчету - через @p asp_report_next(). Отдельный отчет содержит в себе название файла, внутренний номер правила, человеко-читаемое объяснение, номера строки и столбца, получить который можно через API @p asp_report (@ref asp_report). Например:
printf("%s:%u:%u: %s: %s",
asp_report_get_file(report),
asp_report_get_line(report),
asp_report_get_column(report),
asp_report_get_rule_id(report),
asp_report_get_explanation(report));
Отдельно от отчетов о дефектах результат несет в себе подозрительные места - код, который требует прочтения человеком, а не код, который неверен. Доступ к первому такому месту осуществляется через @p asp_analysis_result_get_suspicion(), к следующему - через @p asp_suspicion_next(). Запись содержит категорию, уверенность, правило, две фразы (что необычно и что проверить), позицию и фрагмент кода вокруг (@ref asp_suspicion). Категория, уверенность и правило передаются строками ровно в том виде, в каком их написал анализатор: клиент, собранный до появления нового правила, все равно прочитает запись. Анализ запрашивает этот режим опциями @c suspicious, @c suspicious_all и @c suspicious_categories через @p asp_connection_set_analysis_options(). Подозрительное место никогда не считается дефектом и не меняет статус проверки.
Также в отчете присутствуют результаты анализа идентификации файлов и данные о символах/зависимостях. Обратитесь к примеру (demo.c) для понимания, как пользоваться этой информацией.
Пример отображения данных о символьных локациях¶
Из результатов анализа можно получить данные о символьных локациях или соединенных символьных локациях. Они представлены структурой @p asp_symbol_location и извлекаются функцией @p asp_analysis_result_get_symloc().
/** Print one symbol location */
static void
print_symbol_location(asp_symbol_location *sl)
{
char *name;
asp_file *file;
asp_file *file_next;
asp_symbol *sym;
asp_symbol *sym_next;
name = asp_symbol_location_get_name(sl);
DEMO_OUT(" Group: '%s'", name != NULL ? name : "unnamed");
file = asp_symbol_location_get_file(sl);
if (file != NULL)
{
DEMO_OUT(" Related files:");
while (file != NULL)
{
print_file(file);
/* FIXME: implement asp_file_next */
file_next = asp_file_next(file);
asp_file_free(file);
file = file_next;
}
}
sym = asp_symbol_location_get_symbol(sl);
if (sym != NULL)
{
DEMO_OUT(" Symbols:");
while (sym != NULL)
{
print_symbol(sym);
sym_next = asp_symbol_next(sym);
asp_symbol_free(sym);
sym = sym_next;
}
}
}
/** Print all symbol locations */
static void
print_symbol_locations(asp_analysis_result *aar)
{
asp_symbol_location *loc = asp_analysis_result_get_symloc(aar,
ASP_SYMBOL_LOCATION_KIND_PER_FILE);
asp_symbol_location *loc_next;
if (loc != NULL)
{
DEMO_OUT("Symbols per file:");
while (loc != NULL)
{
print_symbol_location(loc);
loc_next = asp_symbol_location_next(loc);
asp_symbol_location_free(loc);
loc = loc_next;
}
}
loc = asp_analysis_result_get_symloc(aar,
ASP_SYMBOL_LOCATION_KIND_CONNECTED_AREAS);
if (loc != NULL)
{
DEMO_OUT("Connected areas:");
while (loc != NULL)
{
print_symbol_location(loc);
loc_next = asp_symbol_location_next(loc);
asp_symbol_location_free(loc);
loc = loc_next;
}
}
}
Данные о вызовах функций¶
Из результатов анализа можно также извлечь пары вызывающий - вызываемый, реализованные с помощью структуры @p asp_symbol_call. Данную структуру можно получить с помощью функции @p asp_analysis_result_get_symcall().
/** Print a symbol call */
static void
print_symbol_call(asp_symbol_call *sc)
{
asp_symbol *sym;
asp_symbol *sym_next;
DEMO_OUT(" Caller:");
sym = asp_symbol_call_get_caller(sc);
if (sym != NULL)
{
print_symbol(sym);
asp_symbol_free(sym);
}
sym = asp_symbol_call_get_callee(sc);
if (sym != NULL)
{
DEMO_OUT(" Callees:");
while (sym != NULL)
{
print_symbol(sym);
sym_next = asp_symbol_next(sym);
asp_symbol_free(sym);
sym = sym_next;
}
}
}
/** Print symbol call results */
static void
print_symbol_calls(asp_analysis_result *aar)
{
asp_symbol *sym;
asp_symbol *sym_next;
asp_symbol_call *call = asp_analysis_result_get_symcall(aar);
asp_symbol_call *call_next;
if (call != NULL)
{
DEMO_OUT("Call map:");
while (call != NULL)
{
print_symbol_call(call);
call_next = asp_symbol_call_next(call);
asp_symbol_call_free(call);
call = call_next;
DEMO_OUT("----");
}
}
}