go/analysis: как устроена модульная система статического анализа в Go

· Программы
Команда Go документирует пакет golang.org/x/tools/go/analysis — общий интерфейс между анализаторами кода и программами-драйверами. На его основе работают go vet, IDE и другие инструменты.

cover.svg

Пакет golang.org/x/tools/go/analysis описывает интерфейс, который связывает отдельный статический анализатор с программой-драйвером, запускающей его. Идея проста: один раз написанный анализатор можно подключить где угодно — в утилите командной строки вроде go vet, в редакторе или IDE, в системах сборки (go build, Bazel, Buck), в инструментах ревью и индексаторах исходников. От автора требуется реализовать общий контракт, а не подстраиваться под каждый инструмент отдельно.

Анализатор и проход

Центральный тип — Analyzer. По сути это декларативное описание проверки: имя, документация, флаги, зависимости от других анализаторов и функция Run. Классический пример — checker для fmt.Printf, который ловит несоответствия в форматных строках. Чтобы добавить новую проверку в существующий драйвер, достаточно дописать анализатор в список.

Саму работу выполняет функция Run, получающая объект Pass — единицу работы «этот анализатор на этом пакете». Через Pass доступны синтаксические деревья, информация о типах и позиции в исходниках, а обратно возвращаются диагностики через Report. Показательно, что у диагностики намеренно нет поля важности: авторы фреймворка считают, что приоритет сообщений — дело вкуса пользователя, поэтому фильтрацию и ранжирование оставляют драйверу.

Модульность через «факты»

Самая интересная часть — модульность. Анализ, как и компиляция, можно вести раздельно: пакеты обрабатываются по одному, а результаты нижних уровней переиспользуются на верхних. Для этого введено понятие Fact — промежуточное знание вроде «функция log.Fatalf является обёрткой над Printf». Само по себе оно неинтересно, но служит ступенькой к настоящей диагностике: обнаружив такую обёртку, checker начинает строже проверять и её вызовы, в том числе из других пакетов.

Факты сериализуются через encoding/gob и могут передаваться между пакетами и даже между адресными пространствами. Требование к кодированию — детерминированность: сериализованные факты попадают в артефакты сборки, и недетерминизм привёл бы к ложным промахам кэша в системах с адресацией по содержимому.

Тестирование и готовые команды

Для проверки анализаторов есть подпакет analysistest: ожидаемые диагностики задаются прямо в тестовых файлах комментариями // want .... А чтобы превратить анализатор в самостоятельную утилиту, предусмотрены singlechecker (для одной проверки) и multichecker (для набора) — полноценная команда умещается в несколько строк.

В поставке уже идёт обширный набор готовых проверок: от printf, shadow и nilness до fieldalignment, loopclosure и modernize, предлагающего переписать код под современные возможности языка.


Источник: Hacker News

Комментарии

Войдите, чтобы комментировать.

  • Пока нет комментариев.