Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

До конца раздачи бесплатных лицензий осталось 00:59:58. Забрать!

>
>
>
Ошибка, которую никто никогда не...

Ошибка, которую никто никогда не совершал. Ведь так, да?

16 Июл 2026

Уже с первого курса университета — или даже раньше — вы могли догадаться, что писать arr[len(arr)] — плохая затея. И вряд ли так кто-то может ошибиться, правда? Мы тоже так думали, пока не проверили 1000 проектов.

На всякий случай объясню ошибку

Любой разработчик на Go знает, что встроенная функция len() принимает значение и возвращает длину в соответствии с типом значения.

Обратимся к документации и подробнее рассмотрим сигнатуру функции:

func len(v Type) int

Встроенная функция len возвращает длину v в соответствии с типом переданного значения:

  • Массив: количество элементов в v.
  • Указатель на массив: количество элементов в *v (даже если v равно nil).
  • Срез или карта: количество элементов в v; если v равно nil, len(v) равно нулю.
  • Строка: количество байт в v.
  • Канал: количество элементов в очереди (непрочитанных) в буфере канала; если v равно nil, len(v) равно нулю.

Рассмотрим простой пример:

func SimpleExample(arr []int) {
  _ = arr[len(arr)]
}

Индексы коллекций всегда начинаются с 0. Соответственно, для слайса длины n допустимые индексы находятся в диапазоне от 0 до n – 1. И len(arr) вернёт n. Поэтому обращение к arr[len(arr)] всегда приведёт к панике программы.

Что такое V8031 и как мы это нашли?

Небольшое предисловие: если кто-то не знает, мы разрабатываем статический анализатор кода PVS-Studio для C, C++, C# и Java. Совсем недавно у нас закончилась программа раннего доступа к новым анализаторам для Go и JS/TS. Сейчас мы активно готовимся к их релизу, который будет в августе. Примерно в то же время стартует программа раннего доступа для PVS-Studio Atlas — командного решения для управления результатами анализа кода. Следите за новостями.

Возвращаемся к V8031. Это диагностика для Go анализатора. Когда мы её делали, то не думали, что она сможет дать такие хорошие результаты. Даже предполагали, что она может ничего не найти. Возникали мысли: "А вдруг это надуманная ошибка, которую отыщут на любом ревью или любым тестом?" Но по опыту знали, что есть десятки таких типов ошибок, которые вроде бы должны легко находиться, но их пропускают из-за лени, невнимательности или усталости.

Реализовать диагностику, естественно, было легко. С точки зрения реализации она сделана на чистом AST и по минимуму использует наши наработки сверх того, что предоставляют встроенные средства для анализа в Go. Кстати, если захотите, то можете сделать свои собственные диагностические правила. Начать можно с нашей ознакомительной статьи "Как сделать свой статический анализатор для Go?".

После реализации наступает этап тестирования. В нашей команде он состоит из трёх частей:

  • Unit-тестирование;
  • Запуск диагностического правила на нашей выборке проектов с GitHub;
  • Запуск диагностического правила на 1000 самых популярных Go проектах (отбираем по количеству звёзд).

Сейчас нас интересует именно третий этап. Как только мы получили результаты, то сильно удивились. Давайте посмотрим на них.

Результаты

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

Проект №1

bfe — современный балансировщик нагрузки, использующийся в Baidu.

type BasicLitList []*BasicLit
....
func (b BasicLitList) End() token.Pos {
  return b[len(b)].End()
}

Предупреждение PVS-Studio: V8031 Using the call of the 'len' function as index will cause off-by-one error. Consider using 'len(b) - 1' instead. ast.go 138

Проект №2

Ferret — это программируемый механизм извлечения данных и автоматизации для разработчиков.

func (vec *Vector) Push(value core.Value) *Vector {
  slice := vec.getCurrentSlice()
  slice[len(slice)] = value

  return vec
}

Предупреждение PVS-Studio: V8031 Using the call of the 'len' function as index will cause off-by-one error. Consider using 'len(slice) - 1' instead. vector.go 28

Проект №3

fq — инструмент, язык и декодеры для работы с двоичными и текстовыми форматами.

var blockFns = map[uint64]func(d *decode.D, dc *decodeContext){
  ....
  blockTypeInterfaceDescription: func(d *decode.D, dc *decodeContext) {
    typ := d.FieldU16("link_type", format.LinkTypeMap)
    d.FieldU16("reserved")
    d.FieldU32("snap_len")
    d.FieldArray("options", ....)

  dc.interfaceTypes[len(dc.interfaceTypes)] = int(typ)
  },
  ....
}

Предупреждение PVS-Studio: V8031 Using the call of the 'len' function as index will cause off-by-one error. Consider using 'len(dc.interfaceTypes) - 1' instead. pcapng.go 234

Проект №4

Incus — это современная система управления контейнерами и менеджер виртуальных машин.

func qemuRawCfgOverride(conf []cfg.Section, confOverride string) (....) {
  ....
  content := §ionContent{
    comment: sec.Comment,
    entries: entries,
  }

  indexedSection[len(indexedSection)] = content
  orderedSections = append(orderedSections, section{
    name:    sec.Name,
    content: content,
  })
  ....
}

Предупреждение PVS-Studio: V8031 Using the call of the 'len' function as index will cause off-by-one error. Consider using 'len(indexedSection) - 1' instead. driver_qemu_config_override.go 50

А в этом проекте были обнаружены две такие ошибки:

func (f *heartbeatFixture) node() (*state.State, *cluster.Gateway, string) {
  ....
  f.gateways[len(f.gateways)] = gateway
  f.states[gateway] = state
  f.servers[gateway] = server

  return state, gateway, address
}

V8031 Using the call of the 'len' function as index will cause off-by-one error. Consider using 'len(f.gateways) - 1' instead. heartbeat_test.go 253

Отмечу, что некоторые ошибки я встречал по несколько раз в других проектах. Это происходило из-за того, что условный проект 1 включал условный проект 2 в качестве зависимости. Так что можно сказать, что ошибки множатся.

Подводим итог

Вот таким интересным результатом мы хотели поделиться с вами. Вероятность мала, но, возможно, эта ошибка есть в вашем проекте. Не так давно мы рассказывали о ещё одной интересной ошибке в статье "^ != <<". А чтобы код проекта был чистым, вы можете использовать статический анализатор PVS-Studio.

Удачи!

Подписаться на рассылку
Хотите раз в месяц получать от нас подборку вышедших в этот период самых интересных статей и новостей? Подписывайтесь!
Популярные статьи по теме

Комментарии (0)

Следующие комментарии next comments
close comment form