вторник, 21 февраля 2023 г.

Релиз Go 1.20

Релиз Go, версия 1.20, выходит через шесть месяцев после Go 1.19. Большинство его изменений касаются реализации цепочки инструментов, среды выполнения и библиотек. Как всегда, релиз поддерживает обещание совместимости Go 1. Ожидается, что почти все программы Go продолжат компилироваться и работать, как и раньше.

Изменения в языке

Go 1.20 включает четыре изменения языка.

В Go 1.17 добавлены преобразования из среза в указатель массива. Go 1.20 расширяет это, чтобы разрешить преобразования из среза в массив: для среза x теперь можно записать [4]byte(x) вместо *(*[4]byte)(x).

Пакет unsafe определяет три новые функции SliceData, String и StringData. Наряду с Slice из Go 1.17 эти функции теперь предоставляют полную возможность конструировать и деконструировать значения срезов и строк, не завися от их точного представления.

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

Сопоставимые типы (такие как обычные интерфейсы) теперь могут удовлетворять сравнимым ограничениям, даже если аргументы типа не являются строго сравнимыми (сравнение может прерываться во время выполнения). Это позволяет создать экземпляр параметра типа, ограниченного параметром сравнения (например, параметр типа для определяемого пользователем универсального ключа карты) с аргументом типа, не являющимся строго сравнимым, таким как тип интерфейса или составной тип, содержащий тип интерфейса.

Порты


Windows

Go 1.20 — это последний релиз, который будет работать в любой версии Windows 7, 8, Server 2008 и Server 2012. Для Go 1.21 потребуется как минимум Windows 10 или Server 2016.

Darwin и iOS

Go 1.20 — это последний релиз, который будет работать на macOS 10.13 High Sierra или 10.14 Mojave. Go 1.21 потребует macOS 10.15 Catalina или более поздней версии.

FreeBSD/RISC-V

Go 1.20 добавляет экспериментальную поддержку FreeBSD на RISC-V (GOOS=freebsd, GOARCH=riscv64).

Инструменты


Команда go

Каталог $GOROOT/pkg больше не хранит предварительно скомпилированные архивы пакетов для стандартной библиотеки: go install больше не записывает их, сборка go больше не проверяет их, и дистрибутив Go больше не отправляет их. Вместо этого пакеты в стандартной библиотеке собираются по мере необходимости и кэшируются в кэше сборки, как и пакеты вне GOROOT. Это изменение уменьшает размер дистрибутива Go, а также позволяет избежать перекоса инструментальной цепочки C для пакетов, использующих cgo.

Реализация go test -json была улучшена, чтобы сделать ее более надежной. Программы, запускающие go test -json, не нуждаются в каких-либо обновлениях. Программы, которые напрямую вызывают инструмент go test2json, теперь должны запускать двоичный файл теста с параметром -v=test2json (например, go test -v=test2json или ./pkg.test -test.v=test2json) вместо простого -v.

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

Команда go теперь определяет теги построения функций архитектуры, такие как amd64.v2, чтобы позволить выбрать файл реализации пакета на основе наличия или отсутствия конкретной функции архитектуры.

Подкоманды go теперь принимают -C <dir> для изменения каталога на <dir> перед выполнением команды, что может быть полезно для сценариев, которым необходимо выполнять команды в нескольких разных модулях.

Команды go build и go test больше не принимают флаг -i, который устарел, начиная с Go 1.16.

Команда go generate теперь принимает -skip <pattern> для пропуска директив //go:generate, соответствующих <pattern>.

Команда go test теперь принимает параметр -skip <pattern>, чтобы пропустить тесты, подтесты или примеры, соответствующие <pattern>.

Когда основной модуль находится в GOPATH/src, go install больше не устанавливает библиотеки для неосновных пакетов в GOPATH/pkg, а go list больше не сообщает поле Target для таких пакетов. (В модульном режиме скомпилированные пакеты хранятся только в кэше сборки, но из-за ошибки цели установки GOPATH неожиданно остались в силе.)

Команды go build, go install и другие, связанные со сборкой, теперь поддерживают флаг -pgo, который включает оптимизацию на основе профиля. Флаг -pgo указывает путь к файлу профиля. Указание -pgo=auto заставляет команду go искать файл с именем default.pgo в каталоге пакета main и использовать его, если он есть. В настоящее время для этого режима требуется, чтобы в командной строке был указан один main пакет, но планируется снять это ограничение в будущем релизе. Указание -pgo=off отключает оптимизацию на основе профиля.

Команды go build, go install и другие команды, связанные со сборкой, теперь поддерживают флаг -cover, который выполняет сборку указанной цели с инструментированием покрытия кода.

go version

Команда go version -m теперь поддерживает чтение дополнительных типов двоичных файлов Go, в первую очередь библиотек DLL Windows, созданных с помощью go build -buildmode=c-shared, и двоичных файлов Linux без разрешения на выполнение.

Cgo

Команда go теперь отключает cgo по умолчанию в системах без цепочки инструментов C. В частности, когда переменная среды CGO_ENABLED не установлена, переменная среды CC не установлена, а компилятор C по умолчанию (обычно clang или gcc) не найден в пути, CGO_ENABLED по умолчанию имеет значение 0. Как всегда, вы можете переопределить значение по умолчанию, установка CGO_ENABLED явно.

Наиболее важным эффектом изменения по умолчанию является то, что когда Go устанавливается в системе без компилятора C, теперь он будет использовать чистые сборки Go для пакетов в стандартной библиотеке, которые используют cgo, вместо использования предустановленных архивов пакетов (которые были удалены, как указано выше) или попытки использовать cgo и неудаче при этом. Это позволяет Go лучше работать в некоторых средах с минимальным контейнером, а также в macOS, где предустановленные архивы пакетов не использовались для пакетов на основе cgo, начиная с версии Go 1.16.

Пакеты в стандартной библиотеке, использующие cgo, это net, os/user и plugin. В macOS пакеты net и os/user были переписаны, чтобы не использовать cgo: теперь один и тот же код используется для сборок cgo и не-cgo, а также кросс-компилируемых сборок. В Windows пакеты net и os/user никогда не использовали cgo. В других системах сборки с отключенным cgo будут использовать чистую версию Go этих пакетов.

В macOS детектор гонок был переписан, чтобы не использовать cgo: программы с включенным детектором гонок можно создавать и запускать без Xcode. В Linux и других системах Unix, а также в Windows для использования детектора гонки требуется цепочка инструментов C на хосте.

Cover (Покрытие)

Go 1.20 поддерживает сбор профилей покрытия кода для программ (приложений и интеграционных тестов), а не только модульных тестов.

Чтобы собрать данные о покрытии для программы, соберите ее с флагом -cover команды go build, затем запустите полученный двоичный файл с переменной среды GOCOVERDIR, установленной в выходной каталог для профилей покрытия.

Ветеринар (vet)

Улучшено обнаружение захвата переменных цикла вложенными функциями.

Инструмент vet теперь сообщает о ссылках на переменные цикла после вызова T.Parallel() в телах функций подтеста. В таких ссылках может наблюдаться значение переменной из другой итерации (что обычно приводит к пропуску тестовых случаев) или недопустимое состояние из-за несинхронизированного конкурентного доступа.

Инструмент также обнаруживает ошибки ссылок в большем количестве мест. Раньше он рассматривал только последний оператор тела цикла, но теперь он рекурсивно проверяет последние операторы в операторах if, switch и select.

Новая диагностика для некорректных форматов времени

Инструмент vet теперь сообщает об использовании формата времени 2006-02-01 (гггг-дд-мм) с Time.Format и time.Parse. Этот формат не встречается в общих стандартах даты, но часто используется по ошибке при попытке использовать формат даты ISO 8601 (гггг-мм-дд).

Runtime

Некоторые из внутренних структур данных сборщика мусора были реорганизованы, чтобы освободить больше места и CPU. Это изменение снижает нагрузку на память и повышает общую производительность процессора до 2%.

В некоторых случаях сборщик мусора ведет себя менее беспорядочно по отношению к горутине.

Go 1.20 добавляет новый пакет runtime/coverage, содержащий API для записи данных профиля покрытия во время выполнения из долго работающих и/или серверных программ, которые не завершаются через os.Exit().

Компилятор

В Go 1.20 добавлена предварительная поддержка оптимизации на основе профиля (PGO). PGO позволяет цепочке инструментов выполнять оптимизацию для конкретных приложений и рабочих нагрузок на основе информации о профиле времени выполнения. В настоящее время компилятор поддерживает профили CPU pprof, которые можно собирать обычными средствами, такими как пакеты runtime/pprof или net/http/pprof. Чтобы включить PGO, передайте путь к файлу профиля pprof через флаг -pgo, чтобы начать сборку, как указано выше. Go 1.20 использует PGO для более агрессивного встраивания функций в местах горячих вызовов. Тесты для репрезентативного набора программ Go показывают, что включение оптимизации встраивания на основе профиля повышает производительность примерно на 3–4%. Планируется добавить больше оптимизаций на основе профилей в будущих релизах. Обратите внимание, что оптимизация на основе профилей является предварительной версией, поэтому используйте ее с осторожностью.

Компилятор Go 1.20 обновил свой внешний интерфейс, чтобы использовать новый способ обработки внутренних данных компилятора, который устраняет несколько проблем с универсальными типами и позволяет объявлять типы в универсальных функциях и методах.

Компилятор теперь по умолчанию отклоняет циклы анонимного интерфейса с ошибкой компилятора. Они возникают из-за хитрого использования встроенных интерфейсов и всегда имели тонкие проблемы с корректностью, но у разработчиков языка Go нет доказательств того, что они действительно используются на практике. Предполагая, что это изменение не повлияет на отчеты пользователей, планируется обновить спецификацию языка для Go 1.22, чтобы официально запретить их, чтобы авторы инструментов также могли прекратить их поддержку.

В Go 1.18 и 1.19 наблюдался регресс в скорости сборки, в основном из-за добавления поддержки дженериков и последующей работы. Go 1.20 повышает скорость сборки до 10 %, возвращая ее в соответствие с Go 1.17. По сравнению с Go 1.19 производительность сгенерированного кода также в целом немного улучшилась.

Линкер (компоновщик)

В Linux компоновщик теперь выбирает динамический интерпретатор для glibc или musl во время компоновки.

В Windows компоновщик Go теперь поддерживает современные наборы инструментов C на основе LLVM.

Go 1.20 использует префиксы go: и type: для символов, сгенерированных компилятором, а не go. и type. . Это позволяет избежать путаницы для пользовательских пакетов, имя которых начинается с go. . Пакет debug/gosym понимает это новое соглашение об именах для двоичных файлов, созданных с помощью Go 1.20 и новее.

Начальная загрузка (Bootstrap)

При сборке релиза Go из исходного кода и не заданном GOROOT_BOOTSTRAP предыдущие версии Go искали цепочку инструментов начальной загрузки Go 1.4 или более поздней версии в каталоге $HOME/go1.4 (%HOMEDRIVE%%HOMEPATH%\go1.4 в Windows). Go 1.18 и Go 1.19 сначала искали $HOME/go1.17 или $HOME/sdk/go1.17, а затем возвращались к $HOME/go1.4, ожидая, что Go 1.17 потребуется для использования при начальной загрузке Go 1.20. Go 1.20 требует версии Go 1.17 для начальной загрузки, но стало понятно, что следует использовать последнюю точечную версию цепочки инструментов начальной загрузки, поэтому для нее требуется Go 1.17.13. Go 1.20 ищет $HOME/go1.17.13 или $HOME/sdk/go1.17.13, прежде чем вернуться к $HOME/go1.4 (для поддержки систем, которые жестко запрограммировали путь $HOME/go1.4, но установили более новый Go туда). В будущем планируется продвигать цепочку инструментов начальной загрузки примерно раз в год, и, в частности, ожидается, что Go 1.22 потребует финальной версии Go 1.20 для начальной загрузки.

Основная библиотека


Новый пакет crypto/ecdh

Go 1.20 добавляет новый пакет crypto/ecdh для обеспечения явной поддержки обмена ключами Диффи-Хеллмана на эллиптических кривых по кривым NIST и Curve25519.

Программы должны использовать crypto/ecdh вместо функций более низкого уровня в crypto/elliptic для ECDH и сторонние модули для более продвинутых вариантов использования.

Обертывание нескольких ошибок

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

Ошибка e может обернуть более одной ошибки, предоставив метод Unwrap, который возвращает []error.

Функции errors.Is и errors.As были обновлены для проверки многократно обернутых ошибок.

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

Новая функция errors.Join возвращает ошибку, обертывающую список ошибок.

HTTP ResponseController

Новый тип "net/http".ResponseController предоставляет доступ к расширенным функциям для каждого запроса, которые не обрабатываются интерфейсом "net/http".ResponseWriter.

Ранее были добавилены новые функции для каждого запроса, определив необязательные интерфейсы, которые может реализовать ResponseWriter, например Flusher. Эти интерфейсы не обнаруживаются и неуклюжи в использовании.

Тип ResponseController обеспечивает более понятный и удобный способ добавления элементов управления для каждого обработчика. В Go 1.20 также добавлены два таких элемента управления: SetReadDeadline и SetWriteDeadline, которые позволяют устанавливать крайние сроки чтения и записи для каждого запроса. Например:

func RequestHandler(w ResponseWriter, r *Request) {
  rc := http.NewResponseController(w)
  rc.SetWriteDeadline(time.Time{}) // отключаем Server.WriteTimeout при отправке большого ответа
  io.Copy(w, bigData)
}

Новый хук ReverseProxy Rewrite

Прокси-сервер пересылки httputil.ReverseProxy включает новую функцию хука Rewrite, заменяющую предыдущий хук Director.

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

Метод ProxyRequest.SetURL направляет исходящий запрос в указанное место назначения и заменяет функцию NewSingleHostReverseProxy. В отличие от NewSingleHostReverseProxy, SetURL также устанавливает заголовок узла исходящего запроса.

Метод ProxyRequest.SetXForwarded устанавливает заголовки X-Forwarded-For, X-Forwarded-Host и X-Forwarded-Proto исходящего запроса. При использовании Rewrite эти заголовки не добавляются по умолчанию.

Пример хука Rewrite, использующего эти функции:

proxyHandler := &httputil.ReverseProxy{
  Rewrite: func(r *httputil.ProxyRequest) {
    r.SetURL(outboundURL) // Перенаправить запрос на исходящий URL.
    r.SetXForwarded()     //  Установить заголовки X-Forwarded-*.
    r.Out.Header.Set("X-Additional-Header", "header set by the proxy")
  },
}

ReverseProxy больше не добавляет заголовок User-Agent к пересылаемым запросам, если во входящем запросе его нет.

Мелкие изменения в библиотеке

Как всегда, в библиотеку внесены различные незначительные изменения и обновления, сделанные с учетом обещания совместимости с Go 1. Существуют также различные улучшения производительности, не перечисленные здесь.

archive/tar

Если установлена переменная среды GODEBUG=tarinsecurepath=0, метод Reader.Next теперь будет возвращать ошибку ErrInsecurePath для записи с именем файла, которое является абсолютным путем, ссылается на расположение за пределами текущего каталога, содержит недопустимые символы или (в Windows) — это зарезервированное имя, такое как NUL. В будущей версии Go небезопасные пути могут быть отключены по умолчанию.

archive/zip

Если установлена переменная окружения GODEBUG=zipinsecurepath=0, NewReader теперь будет возвращать ошибку ErrInsecurePath при открытии архива, содержащего любое имя файла, которое является абсолютным путем, ссылается на расположение за пределами текущего каталога, содержит недопустимые символы или (на Windows) — это зарезервированные имена, такие как NUL. В будущей версии Go небезопасные пути могут быть отключены по умолчанию.

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

bytes

Новые функции CutPrefix и CutSuffix похожи на TrimPrefix и TrimSuffix, но также сообщают, была ли строка обрезана.

Новая функция Clone выделяет копию байтового среза.

context

Новая функция WithCancelCause предоставляет способ отменить контекст с заданной ошибкой. Эту ошибку можно получить, вызвав новую функцию Cause.

crypto/ecdsa

При использовании поддерживаемых кривых все операции теперь выполняются за постоянное время. Это привело к увеличению процессорного времени от 5% до 30%, в основном затронув P-384 и P-521.

Новый метод PrivateKey.ECDH преобразует ecdsa.PrivateKey в ecdh.PrivateKey.

crypto/ed25519

Метод PrivateKey.Sign и функция VerifyWithOptions теперь поддерживают подписание предварительно хэшированных сообщений с помощью Ed25519ph, на что указывает Options.HashFunc, возвращающий crypto.SHA512. Кроме того, теперь они поддерживают Ed25519ctx и Ed25519ph с контекстом, указанным в новом поле Options.Context.

crypto/rsa

Новое поле OAEPOptions.MGFHash позволяет отдельно настроить хэш MGF1 для расшифровки OAEP.

crypto/rsa теперь использует новый, более безопасный бекенд с постоянным временем. Это приводит к увеличению времени выполнения ЦП для операций расшифровки примерно на 15% (RSA-2048 на amd64) до 45% (RSA-4096 на arm64) и больше на 32-разрядных архитектурах. Операции шифрования примерно в 20 раз медленнее, чем раньше (но все же в 5-10 раз быстрее, чем дешифрование). Ожидается, что производительность улучшится в будущих релизах. Программы не должны изменять или создавать вручную поля PrecomputedValues.

crypto/subtle

Новая функция XORBytes XOR объединяет два байтовых среза.

crypto/tls

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

При сбое рукопожатия из-за сбоя проверки сертификата клиент и сервер TLS теперь возвращают ошибку нового типа CertificateVerificationError, которая включает представленные сертификаты.

crypto/x509

ParsePKCS8PrivateKey и MarshalPKCS8PrivateKey теперь поддерживают ключи типа *crypto/ecdh.PrivateKey. ParsePKIXPublicKey и MarshalPKIXPublicKey теперь поддерживают ключи типа *crypto/ecdh.PublicKey. Анализ ключей кривой NIST по-прежнему возвращает значения типа *ecdsa.PublicKey и *ecdsa.PrivateKey. Используйте их новые методы ECDH для преобразования в типы crypto/ecdh.

Новая функция SetFallbackRoots позволяет программе определить набор резервных корневых сертификатов на случай, если верификатор операционной системы или стандартный корневой пакет платформы недоступен во время выполнения. Чаще всего он будет использоваться с новым пакетом golang.org/x/crypto/x509roots/fallback, который предоставит обновленный корневой пакет.

debug/elf

Попытки чтения из раздела SHT_NOBITS с помощью Section.Data или средства чтения, возвращенного Section.Open, теперь возвращают ошибку.

Дополнительные константы R_LARCH_* определены для использования с системами LoongArch.

Дополнительные константы R_PPC64_* определены для использования с перемещениями PPC64 ELFv2.

Значение константы для R_PPC64_SECTOFF_LO_DS исправлено с 61 на 62.

debug/gosym

В связи с изменением правил именования символов Go инструменты, обрабатывающие двоичные файлы Go, должны использовать пакет debug/gosym версии 1.20 для прозрачной обработки как старых, так и новых двоичных файлов.

debug/pe

Дополнительные константы IMAGE_FILE_MACHINE_RISCV* определены для использования с системами RISC-V.

encoding/binary

Функции ReadVarint и ReadUvarint теперь будут возвращать io.ErrUnexpectedEOF после чтения частичного значения, а не io.EOF.

encoding/xml

Новый метод Encoder.Close можно использовать для проверки незакрытых элементов после завершения кодирования.

Теперь декодер отклоняет имена элементов и атрибутов, содержащие более одного двоеточия, такие как <a:b:c>, а также пространства имен, которые разрешаются в пустую строку, например xmlns:a="".

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

errors

Новая функция Join возвращает ошибку, обертывающую список ошибок.

fmt

Функция Errorf поддерживает несколько вхождений глагола формата %w, возвращая ошибку, которая разворачивается в список всех аргументов %w.

Новая функция FormatString восстанавливает директиву форматирования, соответствующую состоянию, что может быть полезно в Formatter. реализации.

go/ast

Новое поле RangeStmt.Range записывает положение ключевого слова диапазона в операторе диапазона.

В новых полях File.FileStart и File.FileEnd записывается положение начала и конца всего исходного файла.

go/token

Новый метод FileSet.RemoveFile удаляет файл из набора файлов. Долго работающие программы могут использовать это для освобождения памяти, связанной с файлами, которые им больше не нужны.

go/types

Новая функция Satisfies сообщает, удовлетворяет ли тип ограничениям. Это изменение согласуется с новой языковой семантикой, которая отличает удовлетворение ограничения от реализации интерфейса.

io

Новый OffsetWriter является оболочкой базового WriterAt и предоставляет методы Seek, Write и WriteAt, которые корректируют их эффективную позицию смещения в файле на фиксированную величину.

io/fs

Новая ошибка SkipAll немедленно, но успешно завершает WalkDir.

math/big

Широкая область применения пакета math/big и время, зависящее от ввода, делают его плохо подходящим для реализации криптографии. Пакеты криптографии в стандартной библиотеке больше не вызывают нетривиальные методы Int для входных данных, контролируемых злоумышленником. В будущем определение того, считается ли ошибка в math/big уязвимостью системы безопасности, будет зависеть от ее более широкого влияния на стандартную библиотеку.

math/rand

Пакет math/rand теперь автоматически заполняет глобальный генератор случайных чисел (используемый функциями верхнего уровня, такими как Float64 и Int) случайным значением, а функция Seed верхнего уровня устарела. Программы, которым нужна воспроизводимая последовательность случайных чисел, должны предпочитать выделять свой собственный источник случайных чисел, используя rand.New(rand.NewSource(seed)).

Программы, которым требуется более раннее последовательное глобальное заполнение, могут установить GODEBUG=randautoseed=0 в своей среде.

Функция чтения верхнего уровня устарела. Почти во всех случаях лучше подходит crypto/rand.Read.

mime

Функция ParseMediaType теперь позволяет дублировать имена параметров, если значения имен совпадают.

mime/multipart

Методы типа Reader теперь оборачивают ошибки, возвращаемые базовым io.Reader.

net

Функция LookupCNAME теперь последовательно возвращает содержимое записи CNAME, если она существует. Раньше в системах Unix и при использовании чистого преобразователя Go функция LookupCNAME возвращала ошибку, если запись CNAME ссылалась на имя без записи A, AAAA или CNAME. Это изменение изменяет LookupCNAME, чтобы оно соответствовало предыдущему поведению в Windows, позволяя успешно выполнять LookupCNAME всякий раз, когда CNAME существует.

Interface.Flags теперь включает новый флаг FlagRunning, указывающий на операционно активный интерфейс. Для интерфейса, настроенного административно, но неактивного (например, из-за того, что сетевой кабель не подключен), будет установлен FlagUp, но не FlagRunning.

Новое поле Dialer.ControlContext содержит функцию обратного вызова, аналогичную существующему хуку Dialer.Control, который дополнительно принимает контекст набора в качестве параметра. Управление игнорируется, если ControlContext не равен нулю.

Сопоставитель Go DNS распознает вариант сопоставителя trust-ad. Когда в resolv.conf установлены параметры trust-ad, преобразователь Go будет устанавливать бит AD в DNS-запросах. Преобразователь не использует бит AD в ответах.

Разрешение DNS обнаружит изменения в /etc/nsswitch.conf и перезагрузит файл при его изменении. Проверки выполняются не чаще одного раза каждые пять секунд, что соответствует предыдущей обработке файлов /etc/hosts и /etc/resolv.conf.

net/http

Функция ResponseWriter.WriteHeader теперь поддерживает отправку кодов состояния 1xx.

Новый параметр конфигурации Server.DisableGeneralOptionsHandler позволяет отключить обработчик OPTIONS * по умолчанию.

Новый хук Transport.OnProxyConnectResponse вызывается, когда Transport получает HTTP-ответ от прокси-сервера на запрос CONNECT.

HTTP-сервер теперь принимает запросы HEAD, содержащие тело, а не отклоняет их как недействительные.

Ошибки потока HTTP/2, возвращаемые функциями net/http, могут быть преобразованы в golang.org/x/net/http2.StreamError с помощью errors.As.

Начальные и конечные пробелы удаляются из имен файлов cookie, а не отклоняются как недействительные. Например, параметр файла cookie
"имя =значение" теперь принимается как параметр "имя" файла cookie.

net/netip

Новые функции IPv6LinkLocalAllRouters и IPv6Loopback являются эквивалентами net/netip функций net.IPv6loopback и net.IPv6linklocalallrouters.

os

В Windows имя NUL больше не рассматривается как особый случай в Mkdir и Stat.

В Windows File.Stat теперь использует дескриптор файла для получения атрибутов, когда файл является каталогом. Ранее он использовал путь, переданный в Open, который больше не может быть файлом, представленным дескриптором файла, если файл был перемещен или заменен. Это изменение изменяет Open для открытия каталогов без доступа FILE_SHARE_DELETE, что соответствует поведению обычных файлов.

В Windows File.Seek теперь поддерживает поиск в начале каталога.

os/exec

Новые поля Cmd Cancel и WaitDelay определяют поведение Cmd, когда связанный с ним контекст отменяется или его процесс завершается, а каналы ввода-вывода остаются открытыми дочерним процессом.

path/filepath

Новая ошибка SkipAll немедленно, но успешно завершает Walk.

Новая функция IsLocal сообщает, является ли путь лексически локальным для каталога. Например, если IsLocal(p) имеет значение true, то Open(p) будет ссылаться на файл, который лексически находится внутри поддерева с корнем в текущем каталоге.

reflect

Новые методы Value.Comparable и Value.Equal можно использовать для сравнения двух значений на предмет равенства. Comparable сообщает, является ли Equal допустимой операцией для данного приемника Value.

Новый метод Value.Grow расширяет срез, чтобы гарантировать место для других n элементов.

Новый метод Value.SetZero задает нулевое значение для своего типа.

Go 1.18 представил методы Value.SetIterKey и Value.SetIterValue. Это оптимизации: v.SetIterKey(it) должен быть эквивалентен v.Set(it.Key()). Реализации ошибочно пропускали проверку на использование неэкспортированных полей, которая присутствовала в неоптимизированных формах. Go 1.20 исправляет эти методы, чтобы включить проверку неэкспортированных полей.

regexp

Go 1.19.2 и Go 1.18.7 включали исправление безопасности для синтаксического анализатора регулярных выражений, заставляющее его отклонять очень большие выражения, которые потребляли бы слишком много памяти. Поскольку релизы исправлений Go не вводят новый API, синтаксический анализатор в этом случае вернул синтаксис.ErrInternalError. В Go 1.20 добавлена более конкретная ошибка syntax.ErrLarge, которую теперь возвращает синтаксический анализатор.

runtime/cgo

Go 1.20 добавляет новый тип маркера Incomplete. Код, сгенерированный cgo, будет использовать cgo.Incomplete для обозначения неполного типа C.

runtime/metrics

Go 1.20 добавляет новые поддерживаемые метрики, включая текущую настройку GOMAXPROCS (/sched/gomaxprocs:threads), количество выполненных вызовов cgo (/cgo/go-to-c-calls:calls), общее время блокировки мьютекса (/sync/mutex/wait/total:seconds) и различные меры времени, потраченного на сборку мусора.

Метрики гистограмм на основе времени теперь менее точны, но занимают гораздо меньше памяти.

runtime/pprof

Образцы профиля мьютекса теперь предварительно масштабируются, что устраняет проблему, из-за которой старые образцы профиля мьютекса масштабировались неправильно, если частота дискретизации изменялась во время выполнения.

Профили, собранные в Windows, теперь включают информацию о сопоставлении памяти, которая устраняет проблемы с символизацией для позиционно-независимых двоичных файлов.

runtime/trace

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

strings

Новые функции CutPrefix и CutSuffix похожи на TrimPrefix и TrimSuffix, но также сообщают, была ли строка обрезана.

sync

Новые методы Map Swap, CompareAndSwap и CompareAndDelete позволяют автоматически обновлять существующие записи карты.

syscall

Во FreeBSD прокладки совместимости, необходимые для FreeBSD 11 и более ранних версий, были удалены.

В Linux определены дополнительные константы CLONE_* для использования с полем SysProcAttr.Cloneflags.

В Linux новые поля SysProcAttr.CgroupFD и SysProcAttr.UseCgroupFD позволяют поместить дочерний процесс в определенную контрольную группу.

testing

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

time

Новые константы макета времени DateTime, DateOnly и TimeOnly предоставляют имена для трех наиболее распространенных строк шаблона, используемых в обзоре общедоступного исходного кода Go.

Новый метод Time.Compare сравнивает два времени.

Parse теперь игнорирует точность менее наносекунды в своих входных данных, вместо того, чтобы сообщать об этих цифрах как об ошибке.

Метод Time.MarshalJSON теперь более строго соответствует RFC 3339.

unicode/utf16

Новая функция AppendRune добавляет кодировку UTF-16 данной руны к фрагменту uint16, аналогично utf8.AppendRune.


Читайте также:


четверг, 22 сентября 2022 г.

Релиз Go 1.19

Релиз Go, версия 1.19, выходит через пять месяцев после Go 1.18. Большинство его изменений касаются реализации цепочки инструментов, среды выполнения и библиотек. Как всегда, выпуск поддерживает обещание совместимости Go 1. Ожидается, что почти все программы Go продолжат компилироваться и работать, как и раньше.

Изменения в языке

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

Модель памяти

Модель памяти Go была пересмотрена, чтобы привести Go в соответствие с моделью памяти, используемой в C, C++, Java, JavaScript, Rust и Swift. Go предоставляет только последовательно согласованные атомарные элементы, а не какие-либо более простые формы, встречающиеся в других языках. Наряду с обновлением модели памяти в Go 1.19 представлены новые типы в пакете sync/atomic, которые упрощают использование атомарных значений, таких как atomic.Int64 и atomic.Pointer[T].

Порты

64-битная версия LoongArch

Go 1.19 добавляет поддержку 64-битной архитектуры Loongson LoongArch для Linux (GOOS=linux, GOARCH=loong64). Реализованный ABI — LP64D. Минимальная поддерживаемая версия ядра — 5.19.

Обратите внимание, что большинство существующих коммерческих дистрибутивов Linux для LoongArch поставляются со старыми ядрами с исторически несовместимым системным вызовом ABI. Скомпилированные двоичные файлы не будут работать в этих системах, даже если они статически связаны. Пользователи таких неподдерживаемых систем ограничены пакетом Go из дистрибутива.

RISC-V

Порт riscv64 теперь поддерживает передачу аргументов функции и результата с использованием регистров. Сравнительный анализ показывает типичное повышение производительности на 10% и более на riscv64.

Инструменты

Комментарии к документу

В Go 1.19 добавлена поддержка ссылок, списков и более четких заголовков в комментариях к документам. В рамках этого изменения gofmt теперь переформатирует комментарии к документам, чтобы сделать их отображаемый смысл более ясным.В качестве еще одной части этого изменения новый пакет go/doc/comment обеспечивает синтаксический анализ и переформатирование комментариев к документам, а также поддержку их преобразования в HTML, Markdown и текст.

Новое ограничение сборки unix

Unix-ограничение сборки теперь распознается в строках //go:build. Ограничение выполняется, если целевая операционная система, также известная как GOOS, является Unix или Unix-подобной системой. Для версии 1.19 допустимо, если GOOS является одним из следующих: aix, android, darwin, dragonfly, freebsd, hurd, illumos, ios, linux, netbsd, openbsd или solaris. В будущих релизах ограничение unix может соответствовать дополнительным вновь поддерживаемым операционным системам.

Команда go

Флаг -trimpath, если он установлен, теперь включается в настройки сборки, проставленные в двоичных файлах Go командой go build, и может быть проверен с помощью go version -m или debug.ReadBuildInfo.

go generate теперь устанавливает переменную среды GOROOT явно в среде генератора, так что генераторы могут найти правильный GOROOT, даже если он собран с -trimpath.

go test и go generate теперь размещает GOROOT/bin в начало PATH, используемого для подпроцесса, поэтому тесты и генераторы, выполняющие команду go, разрешат ее в один и тот же GOROOT.

go env теперь заключает в кавычки записи, содержащие пробелы в отчетных переменных CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS, CGO_LDFLAGS и GOGCCFLAGS.

go list -json теперь принимает список полей JSON, разделенных запятыми, для заполнения. Если указан список, выходные данные JSON будут включать только эти поля, и список перехода может избежать работы по вычислению полей, которые не включены. В некоторых случаях это может подавить ошибки, о которых в противном случае сообщалось бы.

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

Ветеринар (Vet)

vet чекер "errorsas" теперь сообщает когда errors.As вызывается со вторым аргументом типа *error, что является распространенной ошибкой.

Среда выполнения (Runtime)

Среда выполнения теперь включает поддержку мягкого ограничения памяти. Это ограничение памяти включает кучу (heap) Go и всю другую память, управляемую средой выполнения, и исключает внешние источники памяти, такие как сопоставления самого двоичного файла, память, управляемую на других языках, и память, удерживаемую операционной системой от имени программы Go. Этим ограничением можно управлять с помощью runtime/debug.SetMemoryLimit или эквивалентной переменной среды GOMEMLIMIT. Ограничение работает в сочетании с runtime/debug.SetGCPercent / GOGC и будет соблюдаться, даже если GOGC=off, позволяя программам Go всегда максимально использовать свой лимит памяти, в некоторых случаях повышая эффективность использования ресурсов. Обратите внимание, что небольшие ограничения памяти, порядка десятков мегабайт или меньше, с меньшей вероятностью будут соблюдаться из-за внешних факторов задержки, таких как планирование ОС. Большие пределы памяти, порядка сотен мегабайт и более, стабильны и готовы к работе.

Чтобы ограничить последствия перегрузки GC, когда размер динамической кучи программы приближается к пределу программной памяти, среда выполнения Go также пытается ограничить общее использование ЦП GC до 50%, исключая время простоя, предпочитая использовать больше памяти, а не предотвращая выполнение приложения. На практике ожидается, что это ограничение будет играть роль только в исключительных случаях, и новая метрика среды выполнения /gc/limiter/last-enabled:gc-cycle сообщает, когда это произошло в последний раз.

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

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

В операционных системах Unix программы Go, которые импортируют пакет os, теперь автоматически увеличивают лимит открытых файлов (RLIMIT_NOFILE) до максимально допустимого значения; то есть они изменяют мягкое ограничение, чтобы оно соответствовало жесткому ограничению. Это исправляет искусственно заниженные ограничения, установленные в некоторых системах для совместимости с очень старыми программами на C, использующими системный вызов select. Это ограничение не помогает программам Go, и вместо этого даже простые программы, такие как gofmt, часто не имеют файловых дескрипторов в таких системах при параллельной обработке большого количества файлов. Одним из последствий этого изменения является то, что программы Go, которые, в свою очередь, выполняют очень старые программы C в дочерних процессах, могут запускать эти программы со слишком высоким ограничением. Это можно исправить, установив жесткое ограничение перед вызовом программы Go.

Неисправимые фатальные ошибки (такие как одновременная запись карты или разблокировка разблокированных мьютексов) теперь печатают более простую трассировку, исключая метаданные времени выполнения (эквивалент фатальной паники), если только GOTRACEBACK не установлено значение system или crash. Внутренние трассировки неустранимых ошибок во время выполнения всегда включают полные метаданные независимо от значения GOTRACEBACK.

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

Поддержка очистки адресов, добавленная в Go 1.18, теперь более точно обрабатывает аргументы функций и глобальные переменные.

Компилятор

Теперь компилятор использует таблицу переходов для реализации больших целочисленных и строковых операторов переключения. Улучшения производительности оператора switch различаются, но могут быть на порядка 20% быстрее. (только GOARCH=amd64 и GOARCH=arm64)

Компилятору Go теперь требуется флаг -p=importpath для создания связываемого объектного файла. Это уже предоставлено командой go и Bazel. Любые другие системы сборки, которые напрямую вызывают компилятор Go, также должны убедиться, что они передают этот флаг.

Компилятор Go больше не поддерживает флаг -importmap. Системы сборки, которые напрямую вызывают компилятор Go, должны вместо этого использовать флаг -importcfg.

Ассемблер

Как и компилятору, ассемблеру теперь требуется флаг -p=importpath для создания компоновочного объектного файла. Это уже предоставлено командой go. Любые другие системы сборки, которые напрямую вызывают ассемблер Go, также должны убедиться, что они передают этот флаг.

Линкер

На платформах ELF компоновщик теперь создает сжатые разделы DWARF в стандартном формате gABI (SHF_COMPRESSED) вместо устаревшего формата .zdebug.

Основная библиотека

Новые атомарные типы

Пакет sync/atomic определяет новые атомарные типы Bool, Int32, Int64, Uint32, Uint64, Uintptr и Pointer. Эти типы скрывают базовые значения, поэтому все обращения вынуждены использовать атомарные API. Pointer также избавляет от необходимости конвертировать в unsafe.Pointer в местах вызовов. Int64 и Uint64 автоматически выравниваются по 64-битным границам в структурах и выделенных данных даже в 32-битных системах.

PATH поиск

Command и LookPath больше не позволяют находить результаты поиска PATH относительно текущего каталога. Это устраняет общий источник проблем с безопасностью, но может также нарушить работу существующих программ, которые зависят от использования, скажем, exec.Command("prog") для запуска двоичного файла с именем prog (или, в Windows, prog.exe) в текущем каталоге.

В Windows Command и LookPath теперь учитывают переменную среды NoDefaultCurrentDirectoryInExePath, что позволяет отключить неявный поиск “.” по умолчанию в поиске PATH в системах Windows.

Мелкие изменения в библиотеке

Как всегда, в библиотеку внесены различные незначительные изменения и обновления, сделанные с учетом обещания совместимости с Go 1. Существуют также различные улучшения производительности, не перечисленные здесь.

archive/zip

Reader теперь игнорирует данные, отличные от ZIP, в начале ZIP-файла, что соответствует большинству других реализаций. Это необходимо, помимо прочего, для чтения некоторых файлов Java JAR.

crypto/elliptic

Работа с недопустимыми точками кривой (теми, для которых метод IsOnCurve возвращает false и которые никогда не возвращаются Unmarshal или методом Curve, работающим с допустимой точкой) всегда было неопределенным поведением и может привести к атакам восстановления ключа. Если для Marshal, MarshalCompressed, Add, Double или ScalarMult указана недопустимая точка, они теперь будут паниковать.

Операции ScalarBaseMult с кривыми P224, P384 и P521 теперь выполняются до трех раз быстрее, что приводит к аналогичному ускорению некоторых операций ECDSA. Общая (не оптимизированная для платформы) реализация P256 была заменена реализацией, полученной из официально проверенной модели; это может привести к значительному замедлению работы на 32-разрядных платформах.

crypto/rand

Read больше не буферизует случайные данные, полученные от операционной системы, между вызовами. Приложения, которые выполняют много небольших операций чтения с высокой частотой, могут по соображениям производительности обернуть Reader в bufio.Reader, стараясь использовать io.ReadFull, чтобы гарантировать отсутствие частичных чтений.

В Plan 9 функция Read была повторно реализована, заменив алгоритм ANSI X9.31 на генератор быстрого стирания ключей.

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

crypto/tls

Опция GODEBUG tls10default=1 удалена. По-прежнему можно включить TLS 1.0 на стороне клиента, установив Config.MinVersion.

Сервер и клиент TLS теперь отклоняют повторяющиеся расширения в рукопожатиях TLS, как того требуют RFC 5246, раздел 7.4.1.4 и RFC 8446, раздел 4.2.

crypto/x509

CreateCertificate больше не поддерживает создание сертификатов с SignatureAlgorithm, для которого задано значение MD5WithRSA.

CreateCertificate больше не принимает отрицательные серийные номера.

CreateCertificate больше не будет выдавать пустую SEQUENCE, если созданный сертификат не имеет расширений.

Удаление GODEBUG optionx509sha1=1, первоначально запланированное для Go 1.19, было перенесено на будущий релиз. Приложения, использующие его, должны работать при миграции. Практические атаки на SHA-1 были продемонстрированы с 2017 года, а общедоступные доверенные центры сертификации не выпускали сертификаты SHA-1 с 2015 года.

ParseCertificate и ParseCertificateRequest теперь отклоняют сертификаты и CSR, которые содержат повторяющиеся расширения.

Новые методы CertPool.Clone и CertPool.Equal позволяют клонировать CertPool и проверять эквивалентность двух CertPools соответственно.

Новая функция ParseRevocationList обеспечивает более быстрый и безопасный анализатор CRL, который возвращает RevocationList. При разборе CRL также заполняются новые поля RevocationList RawIssuer, Signature, AuthorityKeyId и Extensions, которые игнорируются CreateRevocationList.

Новый метод RevocationList.CheckSignatureFrom проверяет, является ли подпись в CRL действительной подписью сертификата.

Функции ParseCRL и ParseDERCRL теперь объявлены устаревшими в пользу ParseRevocationList. Метод Certificate.CheckCRLSignature устарел в пользу RevocationList.CheckSignatureFrom.

Построитель пути Certificate.Verify был переработан и теперь должен создавать более качественные цепочки и/или быть более эффективным в сложных сценариях. Ограничения имен теперь также применяются к неконечным сертификатам.

crypto/x509/pkix

Типы CertificateList и TBSCertificateList устарели. Вместо этого следует использовать новую функциональность crypto/x509 CRL.

debug/elf

Новые константы EM_LOONGARCH и R_LARCH_* поддерживают порт loong64.

debug/pe

Новый метод File.COFFSymbolReadSectionDefAux, возвращающий COFFSymbolAuxFormat5, обеспечивает доступ к информации COMDAT в разделах PE-файла. Они поддерживаются новыми константами IMAGE_COMDAT_* и IMAGE_SCN_*.

encoding/binary

Новый интерфейс AppendByteOrder предоставляет эффективные методы добавления uint16, uint32 или uint64 к срезу байтов. BigEndian и LittleEndian теперь реализуют этот интерфейс.

Точно так же новые функции AppendUvarint и AppendVarint являются эффективными версиями добавления функций PutUvarint и PutVarint.

encoding/csv

Новый метод Reader.InputOffset сообщает о текущей позиции ввода средства чтения в виде смещения в байтах, аналогично Decoder.InputOffset в encoding/json.

encoding/xml

Новый метод Decoder.InputPos сообщает текущую позицию ввода считывателя в виде строки и столбца, аналогично Decoder.FieldPos из encoding/csv.

flag

Новая функция TextVar определяет флаг со значением, реализующим encoding.TextUnmarshaler, что позволяет переменным флага командной строки иметь такие типы, как big.Int, netip.Addr и time.Time.

fmt

Новые функции Append, Appendf и Appendln добавляют отформатированные данные в байтовые срезы.

go/parser

Синтаксический анализатор теперь распознает ~x как унарное выражение с оператором token.TILDE, что позволяет лучше устранять ошибки, когда ограничение типа, такое как ~int, используется в неправильном контексте.

go/types

Новые методы Func.Origin и Var.Origin возвращают соответствующий Object универсального типа для синтетических объектов Func и Var, созданных во время создания экземпляра типа.

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

hash/maphash

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

html/template

Тип FuncMap теперь является псевдонимом для FuncMap пакета text/template вместо собственного именованного типа. Это позволяет писать код, который работает с FuncMap при любых настройках.

image/draw

Рисование с оператором Src сохраняет цвета без предварительного умножения альфа-канала, когда целевое и исходное изображения оба имеют формат image.NRGBA или оба изображения image.NRGBA64. Это отменяет изменение поведения, случайно внесенное оптимизацией библиотеки Go 1.18; код теперь соответствует поведению в Go 1.17 и более ранних версиях.

io

Результат NopCloser теперь реализует WriterTo всякий раз, когда это происходит на входе.

Результат MultiReader теперь безоговорочно реализует WriterTo. Если какое-либо базовое средство чтения не реализует WriterTo, оно моделируется соответствующим образом.

mime

Только в Windows пакет mime теперь игнорирует запись реестра о том, что расширение .js должно иметь тип MIME text/plain. Это распространенная непреднамеренная неправильная настройка в системах Windows. В результате .js будет иметь тип MIME text/javascript по умолчанию; кодировка=utf-8. Приложения, которые ожидают text/plain в Windows, теперь должны явно вызывать AddExtensionType.

net

Чистый преобразователь Go теперь будет использовать EDNS(0) для включения рекомендуемой максимальной длины ответного пакета, что позволяет ответным пакетам содержать до 1232 байт (ранее максимальное значение составляло 512). В том маловероятном случае, если это вызовет проблемы с локальным распознавателем DNS, установка переменной среды GODEBUG=netdns=cgo для использования распознавателя на основе cgo должна работать.

Когда функция или метод пакета net возвращает ошибку "I/O timeout", эта ошибка теперь будет удовлетворять errors.Is(err, context.DeadlineExceeded). Когда функция пакета net возвращает ошибку "operation was canceled" ("операция была отменена"), эта ошибка теперь удовлетворяет errors.Is(err, context.Canceled). Эти изменения предназначены для того, чтобы упростить тестирование кода в случаях, когда отмена контекста или таймаут приводят к тому, что функция или метод пакета net возвращает ошибку, сохраняя при этом обратную совместимость для сообщений об ошибках.

Resolver.PreferGo теперь реализован в Windows и Plan 9. Ранее он работал только на платформах Unix. В сочетании с Dialer.Resolver и Resolver.Dial теперь можно писать переносимые программы и контролировать все поиски DNS-имен при подключении.

Пакет net теперь имеет начальную поддержку тега сборки netgo в Windows. При использовании пакет использует DNS-клиент Go (используемый Resolver.PreferGo) вместо того, чтобы запрашивать у Windows результаты DNS. Однако вышестоящий DNS-сервер, который он обнаруживает из Windows, может быть еще неправильным со сложными системными сетевыми конфигурациями.

net/http

ResponseWriter.WriteHeader теперь поддерживает отправку определяемых пользователем информационных заголовков 1xx.

io.ReadCloser, возвращаемый MaxBytesReader, теперь будет возвращать определенный тип ошибки MaxBytesError при превышении предела чтения.

HTTP-клиент будет обрабатывать ответ 3xx без заголовка Location, возвращая его вызывающей стороне, а не рассматривая его как ошибку.

net/url

Новая функция JoinPath и метод URL.JoinPath создают новый URL-адрес путем присоединения к списку элементов пути.

Тип URL-адреса теперь различает URL-адреса без полномочий и URL-адреса с пустыми полномочиями. Например, http:///path имеет пустые права доступа (хост), а в http:/path их нет.

Новое поле URL-адреса OmitHost имеет значение true, если URL-адрес имеет пустые полномочия.

os/exec

Cmd с непустым полем Dir и nil Env теперь неявно устанавливает переменную среды PWD для подпроцесса в соответствие с Dir.

Новый метод Cmd.Environ сообщает о среде, которая будет использоваться для запуска команды, включая неявно заданную переменную PWD.

reflect

Метод Value.Bytes теперь принимает адресные массивы в дополнение к срезам.

Методы Value.Len и Value.Cap теперь успешно работают с указателем на массив и возвращают длину этого массива, чтобы соответствовать тому, что делают встроенные функции len и cap.

regexp/syntax

Go 1.18 релиз-кандидат 1, Go 1.17.8 и Go 1.16.15 включали исправление безопасности для синтаксического анализатора регулярных выражений, заставляющее его отклонять очень глубоко вложенные выражения. Поскольку релизы исправлений Go не вводят новый API, синтаксический анализатор в этом случае вернул синтаксис.ErrInternalError. В Go 1.19 добавлена ​​более конкретная ошибка синтаксис.ErrNestingDepth, которую теперь возвращает синтаксический анализатор.

runtime

Функция GOROOT теперь возвращает пустую строку (вместо "go"), когда бинарный файл был собран с установленным флагом -trimpath и переменная GOROOT не установлена в среде процесса.

runtime/metrics

Новая метрика /sched/gomaxprocs:threads сообщает о текущем значении runtime.GOMAXPROCS.

Новая метрика /cgo/go-to-c-calls:calls сообщает об общем количестве вызовов, сделанных с Go на C. Эта метрика идентична функции runtime.NumCgoCall.

Новая метрика /gc/limiter/last-enabled:gc-cycle сообщает о последнем цикле сборки мусора, когда был включен ограничитель ЦП сборщика мусора.

runtime/pprof

Время пауз Stop-the-world было значительно сокращено при сборе профилей goroutine, что уменьшило общее влияние задержки на приложение.

MaxRSS теперь сообщается в профилях кучи для всех операционных систем Unix (ранее сообщалось только для GOOS=android, darwin, ios и linux).

runtime/race

Детектор гонок был обновлен для использования санитайзера потоков версии v3 на всех поддерживаемых платформах, кроме windows/amd64 и openbsd/amd64, которые остаются на версии v2. По сравнению с v2, теперь он обычно в 1,5–2 раза быстрее, использует вдвое меньше памяти и поддерживает неограниченное количество горутин. В Linux детектор гонки теперь требует как минимум glibc версии 2.17 и GNU binutils 2.26.

Детектор гонки теперь поддерживается на GOARCH=s390x.

Поддержка детектора гонки для openbsd/amd64 была удалена из вышестоящего санитайзера потоков, поэтому вряд ли оно когда-либо будет обновлено с версии v2.

runtime/trace

Если трассировка и профилировщик ЦП включены одновременно, трассировка выполнения включает образцы профиля ЦП как мгновенные события.

sort

Алгоритм сортировки был переписан для использования быстрой сортировки без шаблонов, которая работает быстрее в некоторых распространенных сценариях.

Новая функция Find похожа на Search, но часто проще в использовании: она возвращает дополнительное логическое значение, сообщающее, найдено ли равное значение.

strconv

Quote и связанные с ней функции теперь указывают руну U+007F как \x7f, а не \u007f, для совместимости с другими значениями ASCII.

syscall

На PowerPC (GOARCH=ppc64, ppc64le) Syscall, Syscall6, RawSyscall и RawSyscall6 теперь всегда возвращают 0 для возвращаемого значения r2 вместо неопределенного значения.

В AIX и Solaris теперь определен Getrusage.

time

Новый метод Duration.Abs предоставляет удобный и безопасный способ получения абсолютного значения длительности путем преобразования −2⁶³ в 2⁶³−1. (Этот пограничный случай может произойти в результате вычитания недавнего времени из нулевого времени.)

Новый метод Time.ZoneBounds возвращает время начала и окончания часового пояса, действующего в данный момент времени. Его можно использовать в цикле для перечисления всех известных переходов часовых поясов в заданном месте.


Читайте также:


понедельник, 12 сентября 2022 г.

Начало работы с фаззингом в Golang

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

В этом руководстве вы напишете fuzz тест для простой функции, запустите команду go, отладите и исправите проблемы в коде.

Предпосылки

  • Установленный Go 1.18 или более поздней версии.
  • Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально.
  • Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также с PowerShell или cmd в Windows.
  • Среда, поддерживающая фаззинг. В настоящее время фаззинг с инструментами покрытия доступен только для архитектур AMD64 и ARM64.

Создайте папку для своего кода

Для начала создайте папку для кода, который вы будете писать.

1. Откройте командную строку и перейдите в свой домашний каталог.

В Linux или Mac:

$ cd

В Windows:

C:\> cd %HOMEPATH%

В остальной части руководства в качестве подсказки будет отображаться символ $. Используемые вами команды будут работать и в Windows.

2. В командной строке создайте каталог для своего кода с именем fuzz.

$ mkdir fuzz
$ cd fuzz

3. Создайте модуль для хранения вашего кода.

Запустите команду go mod init, указав путь к модулю вашего нового кода.

$ go mod init example/fuzz
go: creating new go.mod: module example/fuzz

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

Далее вы добавите простой код для переворачивания строки, который мы позже фаззим.

Добавьте код для тестирования

На этом шаге вы добавите функцию для обращения строки.

Напишите код

1. Используя текстовый редактор, создайте файл с именем main.go в каталоге fuzz.

2. В файл main.go в верхней части файла вставьте следующее объявление пакета.

package main

Автономная программа (в отличие от библиотеки) всегда находится в пакете main.

3. Под объявлением пакета вставьте следующее объявление функции.

func Reverse(s string) string {
    b := []byte(s)
    for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return string(b)
}

Эта функция примет строку, обработает ее побайтно и в конце вернет перевернутую строку.

Примечание. Этот код основан на функции stringutil.Reverse из golang.org/x/example.

4. В верхней части main.go, под объявлением пакета, вставьте следующую main функцию, чтобы инициализировать строку, перевернуть ее, распечатать вывод и повторить.

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev := Reverse(input)
    doubleRev := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q\n", rev)
    fmt.Printf("reversed again: %q\n", doubleRev)
}

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

5. main функция использует пакет fmt, поэтому вам нужно будет его импортировать.

Первые строки кода должны выглядеть так:

package main

import "fmt"

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
original: "The quick brown fox jumped over the lazy dog"
reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
reversed again: "The quick brown fox jumped over the lazy dog"

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

Теперь, когда код работает, пришло время его протестировать.

Добавить модульный (юнит) тест

На этом шаге вы напишете базовый модульный тест для функции Reverse.

Напишите код

1. Используя текстовый редактор, создайте файл с именем reverse_test.go в каталоге fuzz.

2. Вставьте следующий код в reverse_test.go.

package main

import (
    "testing"
)

func TestReverse(t *testing.T) {
    testcases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {" ", " "},
        {"!12345", "54321!"},
    }
    for _, tc := range testcases {
        rev := Reverse(tc.in)
        if rev != tc.want {
                t.Errorf("Reverse: %q, want %q", rev, tc.want)
        }
    }
}

Этот простой тест подтвердит, что перечисленные входные строки будут правильно инвертированы.

Запустите код

Запустите модульный тест, используя go test

$ go test
PASS
ok      example/fuzz  0.013s

Далее вы измените модульный тест на fuzz тест.

Добавить fuzz тест

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

В этом разделе вы преобразуете модульный тест в fuzz тест, чтобы вы могли генерировать больше входных данных с меньшими затратами!

Обратите внимание, что вы можете хранить модульные тесты, бенчмарки и fuzz тесты в одном и том же файле *_test.go, но в этом примере вы преобразуете модульный тест в fuzz тест.

Напишите код

В текстовом редакторе замените модульный тест в reverse_test.go следующим fuzz тестом.

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Используйте f.Add для предоставления начального корпуса (seed corpus)
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

У фаззинга также есть несколько ограничений. В своем модульном тесте вы можете предсказать ожидаемый результат функции Reverse и убедиться, что фактический результат соответствует этим ожиданиям.

Например, в тестовом случае Reverse("Hello, world") модульный тест определяет результат как "dlrow,olleH".

При фаззинге вы не можете предсказать ожидаемый результат, так как у вас нет контроля над входными данными.

Однако есть несколько свойств функции Reverse, которые вы можете проверить с помощью fuzz теста. В этом fuzz тесте проверяются два свойства:

  • Двойное обращение строки сохраняет исходное значение
  • Перевернутая строка сохраняет свое состояние как действительное UTF-8.

Обратите внимание на синтаксические различия между модульным тестом и fuzz тестом:

  • Функция начинается с FuzzXxx вместо TestXxx и принимает *testing.F вместо *testing.T.
  • Там, где вы ожидаете увидеть выполнение t.Run, вместо этого вы видите f.Fuzz, который принимает целевую функцию фаззинга с параметрами *testing.T и типами, подлежащими фаззингу. Входные данные из вашего модульного теста предоставляются как входные данные начального корпуса с помощью f.Add.

Убедитесь, что новый пакет unicode/utf8 импортирован.

package main

import (
    "testing"
    "unicode/utf8"
)

Когда модульный тест преобразован в fuzz тест, пришло время снова запустить тест.

Запустите код

1. Запустите fuzz тест без фаззинга, чтобы убедиться, что исходные входные данные пройдены.

$ go test
PASS
ok      example/fuzz  0.013s

Вы также можете запустить go test -run=FuzzReverse, если у вас есть другие тесты в этом файле, и вы хотите запустить только fuzz тест.

2. Запустите FuzzReverse с фаззингом, чтобы увидеть, не приведут ли какие-либо случайно сгенерированные вводы строк к сбою. Это выполняется с помощью go test с новым флагом -fuzz.

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
fuzz: minimizing 38-byte failing input file...
--- FAIL: FuzzReverse (0.01s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"

    Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
    To re-run:
    go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
FAIL
exit status 1
FAIL    example/fuzz  0.030s

Во время фаззинга произошел сбой, и ввод, вызвавший проблему, записывается в файл начального корпуса (seed corpus), который будет запущен при следующем вызове go test, даже без флага -fuzz. Чтобы просмотреть ввод, вызвавший сбой, откройте файл корпуса, записанный в каталог testdata/fuzz/FuzzReverse, в текстовом редакторе. Ваш файл исходного корпуса может содержать другую строку, но формат будет таким же.

go test fuzz v1
string("泃")

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

3. Снова запустите go test без флага -fuzz; будет использоваться новая ошибочная запись корпуса исходных данных:

$ go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s)
        reverse_test.go:20: Reverse produced invalid string
FAIL
exit status 1
FAIL    example/fuzz  0.016s

Поскольку наш тест провалился, пришло время отладки.

Исправить ошибку неверной строки

В этом разделе вы отладите сбой и исправите ошибку.

Диагностируйте ошибку

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

В этом руководстве мы будем регистрировать полезную информацию об отладке на вашем терминале.

Во-первых, рассмотрим документацию для utf8.ValidString.

ValidString сообщает, состоит ли s полностью из допустимых рун в кодировке UTF-8.

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

Чтобы выяснить, почему ввод (в данном случае китайский иероглиф 泃) приводит к тому, что Reverse создает недопустимую строку при переворачивании, вы можете проверить количество рун в перевернутой строке.

Напишите код

В текстовом редакторе замените цель fuzz в FuzzReverse следующим.

f.Fuzz(func(t *testing.T, orig string) {
    rev := Reverse(orig)
    doubleRev := Reverse(rev)
    t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))
    if orig != doubleRev {
        t.Errorf("Before: %q, after: %q", orig, doubleRev)
    }
    if utf8.ValidString(orig) && !utf8.ValidString(rev) {
        t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
    }
})

Здесь строка t.Logf будет выводиться в командную строку в случае возникновения ошибки или при выполнении теста с ключом -v, что может помочь вам отладить эту конкретную проблему.

Запустите код

Запустите тест, используя go test

$ go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
        reverse_test.go:16: Number of runes: orig=1, rev=3, doubleRev=1
        reverse_test.go:21: Reverse produced invalid UTF-8 string "\x83\xb3\xe6"
FAIL
exit status 1
FAIL    example/fuzz    0.598s

Во всем начальном корпусе (seed corpus) использовались строки, в которых каждый символ представлял собой один байт. Однако для таких символов, как 泃, может потребоваться несколько байтов. Таким образом, обращение строки байт за байтом сделает недействительными многобайтовые символы.

Примечание. Если вам интересно, как Go работает со строками, прочтите запись в блоге Строки, байты, руны и символы в Go для более глубокого понимания.

Лучше поняв ошибку, исправьте ошибку в функции Reverse.

Исправьте ошибку

Чтобы исправить функцию Reverse, пройдемся по строке по рунам, а не по байтам.

Напишите код

В текстовом редакторе замените существующую функцию Reverse() следующей.

func Reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

Ключевое отличие состоит в том, что Reverse теперь перебирает каждую руну в строке, а не каждый байт.

Запустите код

Запустите тест, используя go test

$ go test
PASS
ok      example/fuzz  0.016s

Теперь тест проходит!

Еще раз проверьте его с помощью go test -fuzz, чтобы увидеть, есть ли какие-либо новые ошибки.

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/37 completed
fuzz: minimizing 506-byte failing input file...
fuzz: elapsed: 0s, gathering baseline coverage: 5/37 completed
--- FAIL: FuzzReverse (0.02s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:33: Before: "\x91", after: "�"

    Failing input written to testdata/fuzz/FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
    To re-run:
    go test -run=FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
FAIL
exit status 1
FAIL    example/fuzz  0.032s

Мы можем видеть, что строка отличается от оригинала после того, как дважды была перевернута. На этот раз сам ввод является недопустимым юникодом. Как это возможно, если мы фаззим со строками?

Давайте снова отладим.

Исправить ошибку двойного реверса

В этом разделе вы отладите ошибку двойного реверса и исправите ее.

Диагностируйте ошибку

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

В этом руководстве мы будем регистрировать полезную информацию об отладке в функции Reverse.

Посмотрите внимательно на перевернутую строку, чтобы найти ошибку. В Go строка представляет собой срез байтов, доступный только для чтения, и может содержать байты, которые не являются допустимыми UTF-8. Исходная строка представляет собой срез байтов с одним байтом, '\x91'. Когда входная строка имеет значение []rune, Go кодирует байтовый срез в UTF-8 и заменяет байт символом UTF-8 �. Когда мы сравниваем замещающий символ UTF-8 с входным байтовым срезом, они явно не равны.

Напишите код

В текстовом редакторе замените функцию Reverse на следующую.

func Reverse(s string) string {
    fmt.Printf("input: %q\n", s)
    r := []rune(s)
    fmt.Printf("runes: %q\n", r)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

Это поможет нам понять, что происходит не так при преобразовании строки в срез рун.

Запустите код

На этот раз мы хотим запустить только неудачный тест, чтобы проверить логи. Для этого мы будем использовать go test -run.

$ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0
input: "\x91"
runes: ['�']
input: "�"
runes: ['�']
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
        reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1
        reverse_test.go:18: Before: "\x91", after: "�"
FAIL
exit status 1
FAIL    example/fuzz    0.145s

Чтобы запустить конкретную запись корпуса в FuzzXxx/testdata, вы можете указать {FuzzTestName}/{filename} для -run. Это может быть полезно при отладке.

Зная, что ввод является недопустимым юникодом, давайте исправим ошибку в нашей функции Reverse.

Исправьте ошибку

Чтобы решить эту проблему, давайте вернем ошибку, если ввод в Reverse недействителен в кодировке UTF-8.

Напишите код

В текстовом редакторе замените существующую функцию Reverse на следующую.

func Reverse(s string) (string, error) {
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}

Это изменение вернет ошибку, если входная строка содержит символы, недопустимые в кодировке UTF-8.

Поскольку функция Reverse теперь возвращает ошибку, измените main функцию, чтобы отбросить дополнительное значение ошибки. Замените существующую main функцию следующей.

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

Эти вызовы Reverse должны возвращать нулевую ошибку, поскольку входная строка является допустимой UTF-8.

Вам нужно будет импортировать пакеты errors и unicode/utf8. Оператор импорта в main.go должен выглядеть следующим образом.

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

Измените файл reverse_test.go, чтобы проверить наличие ошибок и пропустить тест, если ошибки генерируются при возврате.

func FuzzReverse(f *testing.F) {
    testcases := []string {"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  //  Используйте f.Add для предоставления начального корпуса (seed corpus)
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        if err1 != nil {
            return
        }
        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
             return
        }
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

Вместо возврата вы также можете вызвать t.Skip(), чтобы остановить выполнение этого fuzz ввода.

Запустите код

Запустите тест, используя go test

$ go test
PASS
ok      example/fuzz  0.019s

Фаззите с помощью go test -fuzz=Fuzz, затем через несколько секунд прекратите фаззинг, нажав Ctrl-C.

$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workers
fuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35)
fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37)
fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37)
...
fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41)
^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41)
PASS
ok      example/fuzz  228.000s

fuzz тест будет выполняться до тех пор, пока не обнаружит ошибочный ввод, если только вы не передадите флаг -fuzztime. По умолчанию выполняется вечно, если не происходит сбоев, и процесс можно прервать с помощью Ctrl-C.

Проведите фаззинг с помощью go test -fuzz=Fuzz -fuzztime 30s, который будет фаззить в течение 30 секунд перед выходом, если сбоев не обнаружено.

$ go test -fuzz=Fuzz -fuzztime 30s
fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed
fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workers
fuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12)
fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14)
fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14)
fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14)
fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15)
fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15)
fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15)
fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16)
fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17)
fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17)
fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17)
PASS
ok      example/fuzz  31.025s

Фаззинг пройден!

Завершенный код — main.go —

package main

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

func Reverse(s string) (string, error) {
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}

— reverse_test.go —

package main

import (
    "testing"
    "unicode/utf8"
)

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc) // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        if err1 != nil {
            return
        }
        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
            return
        }
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}


Читайте также:


пятница, 20 мая 2022 г.

Начало работы с дженериками в Golang

В этом руководстве представлены основы дженериков в Go. С помощью дженериков вы можете объявлять и использовать функции или типы, которые написаны для работы с любым из набора типов, предоставляемых вызывающим кодом.

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

Предпосылки

  • Установка Go 1.18 или более поздней версии.
  • Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально.
  • Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также с PowerShell или cmd в Windows.

Создайте папку для своего кода

Для начала создайте папку для кода, который вы будете писать.

1. Откройте командную строку и перейдите в свой домашний каталог.

В Linux или Mac:

$ cd

В Windows:

C:\> cd %HOMEPATH%

В остальной части руководства в качестве подсказки будет отображаться символ $. Используемые вами команды будут работать и в Windows.

2. В командной строке создайте каталог для своего кода с именем generics.

$ mkdir generics
$ cd generics

3. Создайте модуль для хранения вашего кода.

Запустите команду инициализации go mod, указав путь к модулю вашего нового кода.

$ go mod init example/generics
go: creating new go.mod: module example/generics

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

Далее вы добавите простой код для работы с картами.

Добавьте неуниверсальные функции

На этом шаге вы добавите две функции, каждая из которых суммирует значения карты и возвращает итог.

Вы объявляете две функции вместо одной, потому что работаете с картами двух разных типов: в одной хранятся значения int64, а в другой хранятся значения float64.

Напишите код

1. Используя текстовый редактор, создайте файл с именем main.go в каталоге generics. Вы будете писать свой код Go в этом файле.

2. В файл main.go в верхней части файла вставьте следующее объявление пакета.

package main

Автономная программа (в отличие от библиотеки) всегда находится в пакете main.

3. Под объявлением пакета вставьте следующие два объявления функций.

// SumInts суммирует значения m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats суммирует значения m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

В этом коде вы объявляете две функции для сложения значений карты и возврата суммы.

SumFloats преобразует строку в значения float64.

SumInts преобразует строку в значения int64.

4. В верхней части main.go под объявлением пакета вставьте следующую main функцию, чтобы инициализировать две карты и использовать их в качестве аргументов при вызове функций, которые вы объявили на предыдущем шаге.

func main() {
    // Инициализировать карту для целочисленных значений
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Инициализируем карту для значений с плавающей запятой
    floats := map[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

В этом коде вы:

  • Инициализируете карту значений float64 и карту значений int64, каждая с двумя записями.
  • Вызывате две объявленные ранее функции, чтобы найти сумму значений каждой карты.
  • Распечатываете результат.

5. В верхней части main.go, прямо под объявлением пакета, импортируйте пакет, который вам понадобится для поддержки кода, который вы только что написали.

Первые строки кода должны выглядеть так:

package main

import "fmt"

6. Сохраните main.go.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97

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

Добавьте общую функцию для обработки нескольких типов

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

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

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

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

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

Имейте в виду, что параметр типа должен поддерживать все операции, выполняемые над ним универсальным кодом. Например, если бы код вашей функции пытался выполнить строковые операции (например, индексацию) над параметром типа, ограничение которого включало числовые типы, код не скомпилировался бы.

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

Напишите код

1. Под двумя функциями, которые вы добавили ранее, вставьте следующую общую функцию.

// SumIntsOrFloats суммирует значения карты m.
// Поддерживает как int64, так и float64 
// как типы для значений карты.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

В этом коде вы:

  • Объявите функцию SumIntsOrFloats с двумя параметрами типа (в квадратных скобках), K и V, и одним аргументом, который использует параметры типа, m типа map[K]V. Функция возвращает значение типа V.
  • Укажете для параметра типа K сопоставимое ограничение типа. Предназначенное специально для таких случаев, сопоставимое ограничение уже объявлено в Go. Он допускает любой тип, значения которого можно использовать в качестве операнда операторов сравнения == и !=. Go требует, чтобы ключи сопоставления были сопоставимы. Поэтому необходимо объявить K сопоставимым, чтобы вы могли использовать K в качестве ключа в переменной карты. Это также гарантирует, что вызывающий код использует допустимый тип для ключей сопоставления.
  • Укажете для параметра типа V ограничение, представляющее собой объединение двух типов: int64 и float64. Использование | задает объединение двух типов, что означает, что это ограничение допускает любой тип. Любой тип будет разрешен компилятором в качестве аргумента в вызывающем коде.
  • Укажете, что аргумент m имеет тип map[K]V, где K и V — это типы, уже указанные для параметров типа. Обратите внимание, что мы знаем, что map[K]V является допустимым типом карты, потому что K является сопоставимым типом. Если бы мы не объявили K сопоставимым, компилятор отклонил бы ссылку на map[K]V.

2. В main.go под уже имеющимся кодом вставьте следующий код.

fmt.Printf("Generic Sums: %v and %v\n",
    SumIntsOrFloats[string, int64](ints),
    SumIntsOrFloats[string, float64](floats))

В этом коде вы:

  • Вызовете общую функцию, которую вы только что объявили, передав каждую из созданных вами карт.
  • Укажете аргументы типа — имена типов в квадратных скобках — чтобы было ясно, какие типы должны заменить параметры типа в вызываемой вами функции.
    Как вы увидите в следующем разделе, вы часто можете опустить аргументы типа в вызове функции. Go часто может вывести их из вашего кода.
  • Выведете суммы, возвращаемые функцией.
Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97

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

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

Удалить аргументы типа при вызове универсальной функции

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

Вы можете опустить аргументы типа в вызывающем коде, когда компилятор Go может вывести типы, которые вы хотите использовать. Компилятор выводит аргументы типа из типов аргументов функции.

Обратите внимание, что это не всегда возможно. Например, если вам нужно вызвать универсальную функцию без аргументов, вам нужно будет включить аргументы типа в вызов функции.

Напишите код

В main.go под уже имеющимся кодом вставьте следующий код.

fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
    SumIntsOrFloats(ints),
    SumIntsOrFloats(floats))

В этом коде вы вызовете универсальную функцию, опуская аргументы типа.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97

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

Объявите ограничение типа

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

Вы объявляете ограничение типа как интерфейс. Ограничение позволяет любому типу реализовать интерфейс. Например, если вы объявляете интерфейс ограничения типа с тремя методами, а затем используете его с параметром типа в универсальной функции, аргументы типа, используемые для вызова функции, должны иметь все эти методы.

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

Напишите код

1. Чуть выше main функции, сразу после операторов импорта, вставьте следующий код, чтобы объявить ограничение типа.

type Number interface {
    int64 | float64
}

В этом коде вы:

  • Объявите тип интерфейса Number для использования в качестве ограничения типа.
  • Объявите объединение int64 и float64 внутри интерфейса.
    По сути, вы перемещаете объединение из объявления функции в ограничение нового типа. Таким образом, если вы хотите ограничить параметр типа значением int64 или float64, вы можете использовать это ограничение числового типа вместо записи int64 | float64.

2. Под уже имеющимися функциями вставьте следующую общую функцию SumNumbers.

// SumNumbers суммирует значения карты m. 
// Поддерживает целые числа и 
// числа с плавающей точкой как значения карты.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

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

3. В main.go под уже имеющимся кодом вставьте следующий код.

fmt.Printf("Generic Sums with Constraint: %v and %v\n",
    SumNumbers(ints),
    SumNumbers(floats))

В этом коде вы вызовете SumNumbers с каждой картой, выводя сумму из значений каждой.

Как и в предыдущем разделе, вы опускаете аргументы типа (имена типов в квадратных скобках) в вызовах универсальной функции. Компилятор Go может вывести аргумент типа из других аргументов.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97

Завершенный код

Вы можете запустить эту программу на игровой площадке Go. На игровой площадке просто нажмите кнопку Run.

package main

import "fmt"

type Number interface {
    int64 | float64
}

func main() {
    // Инициализируем карту для целочисленных значений
    ints := map[string]int64{
        "first": 34,
        "second": 12,
    }

    // Инициализируем карту для значений с плавающей точкой
    floats := map[string]float64{
        "first": 35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
}

// SumInts суммирует значения m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats суммирует значения m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

// SumIntsOrFloats суммирует значения карты m. 
// Поддерживает как числа с плавающей запятой, так и целые числа
// как значения карты.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

// SumNumbers суммирует значения карты m. 
// Поддерживает как числа с плавающей запятой, так и целые числа
// как значения карты.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

Вывод:

Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97


Читайте также: