Протокол сервера статического анализа (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("----");
        }
    }
}