Мой вклад в популярный пакет с открытым исходным кодом вызвал панику в проектах Golang

Здравствуйте, сегодня я расскажу вам, как моя разработка для популярного проекта с открытым исходным кодом вызвала ошибки у людей, использующих этот проект по всему миру, и чему я научился в результате этого процесса.

Контент
— Проект Swaggo
— Наше требование и мой вклад
— Проблема
— Причина проблемы
— Определение функции без тела (go:linkname)
— Решение
— Извлеченные уроки


Проект SWAGGO

Создание документов swagger для ваших проектов API, разработанных на языке Go, немного отличается от таких языков, как Java, C#. Как правило, вы можете создать его, добавляя директивы Swagger в строки комментариев к написанному вами коду, и у вас не так много альтернатив для этого.

Проект swaggo/swag является одним из самых популярных и широко используемых решений.

Основной процесс этого проекта заключается в создании документа swagger путем просмотра файлов Go и обработки строк комментариев в определениях. Для этого создается абстрактное синтаксическое дерево (AST) и осуществляется навигация по нему.


Наши требования и мой вклад

Когда мы с коллегой по команде пытались создать swagger-документ для проекта, мы поняли, что не можем создать нужное нам определение с помощью инструмента swaggo/swag.

Мы хотели определить объекты запроса и ответа внутри созданной нами функции-обработчика и убедиться, что доступ к ним возможен только из этой функции. Но когда мы это сделали, пакет парсера swaggo/swag не смог получить доступ к соответствующим объектам запроса и ответа.

Я хотел внести свой вклад в библиотеку swaggo/swag, чтобы решить эту проблему.

Прежде всего, я открыл проблему в библиотеке и спросил, нужна ли нам такая вещь и что я хочу внести свой вклад, сделав эту разработку.
В ответ они сказали, что разбор частных определений невозможен, а когда они спросили, как я это сделаю, им стало интересно мое предложение, и они ждут, когда я его рассмотрю.

Я засучил рукава. При первой же возможности я сделал разработку и открыл PR для проекта.

В результате сделанной мной разработки объекты запроса/ответа стали определяемыми в функции. 💪

добавить функцию scoped struct parse, тесты #1283

mstrYoda posted on

Опишите ПРИ добавлении функциональности разбора функций scoped structs.

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

package main

// @Param request body main.Fun.request true "query params" 
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun()  {
    type request struct {
        Name string
    }
    
    type response struct {
        Name string
        Child string
    }
}

Relation issuehttps://github.com/swaggo/swag/issues/1274

Дополнительный контекстВсе тесты пройдены в parser_tests.go. Изменения выглядят обратно совместимыми.

Посмотреть на GitHub


Проблема

Вечер пятницы… Работа закончена, есть минуты… Я с нетерпением жду выходных… И… Я получил сообщение от своего товарища по команде, что один из наших проектов получил ошибку 😞.

На самом деле я хочу поделиться полным текстом разговора:

Перевод:

  • Вурал: братан я тебе кое-что скажу
  • Я: да, брат
  • Вурал: один из наших сервисов получает ошибку при генерации swagger do, и угадай на какой части 😀
  • Я: это из-за моей разработки asdfasdf
  • Vural: да, наверное, asdfsadfs
  • Я: ты не можешь быть серьезным asdfasdf

Мы сразу же пошли на страницу проекта на Github и посмотрели на проблемы. И то, что мы увидели, заставило меня немного поволноваться.

Многие пользователи пакетов swaggo/swag сталкивались с ошибками, и все они получали ошибки в конвейерах CI/CD или при ручной генерации swagger благодаря моей разработке.

Ошибка сегментации при выполнении `swag init` после релиза 1.8.5 #1309

mackrorysd posted on

Опишите ошибкуПосле выхода 1.8.5 при выполнении swag init --parseDependency true на моем коде возникает ошибка сегментации. После переключения обратно на 1.8.4 (или другие версии в линейке 1.8) команда завершается успешно.

Для воспроизведенияЯ не могу поделиться всем кодом, на котором это происходит. Я посмотрю, смогу ли я изолировать это в небольшом примере кода, которым я могу поделиться.

Трассировка стека

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x85c8a8]

goroutine 1 [running]:
github.com/swaggo/swag.(*PackagesDefinitions).parseFunctionScopedTypesFromFile(0xc00000e7c8, 0xc00035b980, {0xc0003e15c0, 0x18}, 0xc000cff680)
    /home/sean/go/pkg/mod/github.com/swaggo/swag@v1.8.5/packages.go:168 +0xa8
github.com/swaggo/swag.(*PackagesDefinitions).ParseTypes(0xc00000e7c8)
    /home/sean/go/pkg/mod/github.com/swaggo/swag@v1.8.5/packages.go:110 +0xc9
github.com/swaggo/swag.(*Parser).ParseAPIMultiSearchDir(0xc0001e22a0, {0xc0001d7fb0?, 0x1?, 0x0?}, {0x9587e8?, 0x7?}, 0x64)
    /home/sean/go/pkg/mod/github.com/swaggo/swag@v1.8.5/parser.go:362 +0x3bf
github.com/swaggo/swag/gen.(*Gen).Build(0xc0001db980, 0xc0001e1790)
    /home/sean/go/pkg/mod/github.com/swaggo/swag@v1.8.5/gen/gen.go:177 +0x5c9
main.initAction(0xc0001af680?)
    /home/sean/go/pkg/mod/github.com/swaggo/swag@v1.8.5/cmd/swag/main.go:151 +0x757
github.com/urfave/cli/v2.(*Command).Run(0xc0001ad560, 0xc0001ec580)
    /home/sean/go/pkg/mod/github.com/urfave/cli/v2@v2.3.0/command.go:163 +0x5bb
github.com/urfave/cli/v2.(*App).RunContext(0xc000204000, {0xa25860?, 0xc0000240f0}, {0xc000020080, 0x4, 0x4})
    /home/sean/go/pkg/mod/github.com/urfave/cli/v2@v2.3.0/app.go:313 +0xb48
github.com/urfave/cli/v2.(*App).Run(...)
    /home/sean/go/pkg/mod/github.com/urfave/cli/v2@v2.3.0/app.go:224
main.main()
    /home/sean/go/pkg/mod/github.com/swaggo/swag@v1.8.5/cmd/swag/main.go:221 +0x55d

Версия вашего swag1.8.5 (1.8.4 и более ранние версии работают)

Версия Go: 1.18

Рабочий стол (пожалуйста, заполните следующую информацию):

  • OS: Pop! OS 22.04 (по сути, Ubuntu)
Посмотреть на GitHub

Честно говоря, я был удивлен этой ситуацией, потому что я написал модульные тесты для своей разработки, и код работал корректно. Фактически, успешное прохождение текущих юнит-тестов доказывало, что они не ломали предыдущую разработку.

Так в чем же может быть проблема? 🤔


Причина проблемы

Я прилагаю скриншот из моей разработки для swaggo/swag.

Как видно из сообщения об ошибке, ошибка возникает в методе parseFunctionScopedTypesFromFile. На самом деле, точный номер строки, на которой произошла ошибка, равен 168.

Теперь давайте подробнее рассмотрим этот метод, который я написал.

В строке 168 происходит обращение к AST, и если среди определений есть FunctionDecleration, то то, что написано внутри определенной функции, итерируется с помощью цикла for.

Итак, если мы уверены, что определение функции существует, почему мы получаем ошибку при попытке итерировать это определение функции с помощью цикла for?
Что вызывает ошибку с указателем nil?

Когда мы смотрим на переменную functionDeclaration.Body, мы видим, что это тип указателя.

Я не делал здесь проверку на nil, потому что предполагал, что если функция определена, то она будет иметь определение тела.

Но в реальности это может быть не так.👇


Определение функции без тела

Звучит ли этот термин для вас странно? Можно ли определить функцию, не написав ее тело?

Рассмотрите приведенный ниже код, как вы думаете, является ли он корректным определением в Go?

Получим ли мы ошибку, если попытаемся его выполнить?

Да, как вы догадались, мы не можем запустить или даже скомпилировать этот код, потому что он не является допустимым определением.

Можем ли мы выполнить код ниже?

Здесь, в отличие от предыдущего случая, ответ будет положительным, мы можем его выполнить.

Малоизвестной особенностью Go является то, что мы можем связать определение функции с определениями функций других пакетов с помощью директивы go:linkname.

Таким образом, мы можем определить функцию, не определяя ее тело.

И если вы заметили, эта возможность позволила мне использовать частную функцию, определенную в пакете Go runtime.

Внешние зависимости, которые мы используем в наших проектах, могут иметь такие определения функций. И когда мы пытались создать swagger-документ с помощью команды swag --parseDependency, функции с таким определением вызывали панику в моей разработке, потому что она также разбирала зависимости.


Решение

Я собирался сразу же сделать разработку для исправления проблемы, но, как хорошо, что сообщество разработчиков с открытым исходным кодом, другой разработчик написал код, который исправил ошибку раньше меня, и создал PR.

исправление: проверка тела funcDeclaration #1310

rytsh posted on

Опишите проверку указателя PRNil в скопированных типах функций.

Проблема взаимосвязи#1309

Это исправляет проблему при запуске swag-сборки.

Посмотреть на GitHub


Извлеченные уроки

С помощью этой проблемы я извлек для себя несколько уроков.

✅ Никогда не обходите проверку указателя nil гипотетически.
✅ Юнит-тестов не всегда бывает достаточно. Проводите сквозное тестирование
✅ Мы можем писать функции без определения тела, используя go:linkname в Go, мы можем импортировать функции из частных пакетов
✅ Никогда не указывайте в зависимостях пакета последнюю версию, указывайте версию, в работоспособности которой вы уверены.


Надеюсь, это был интересный и полезный материал для вас. Берегите себя, желаю вам кодов без ошибок 🙏


Let's Connect
Вход в полноэкранный режим Выйти из полноэкранного режима

Twitter
Github

Вы можете поддержать меня на Github: Support mstrYoda on Github

Оцените статью
devanswers.ru
Добавить комментарий