До конца раздачи бесплатных лицензий осталось 00:59:58. Забрать!
Уже с первого курса университета — или даже раньше — вы могли догадаться, что писать 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)] всегда приведёт к панике программы.
Небольшое предисловие: если кто-то не знает, мы разрабатываем статический анализатор кода PVS-Studio для C, C++, C# и Java. Совсем недавно у нас закончилась программа раннего доступа к новым анализаторам для Go и JS/TS. Сейчас мы активно готовимся к их релизу, который будет в августе. Примерно в то же время стартует программа раннего доступа для PVS-Studio Atlas — командного решения для управления результатами анализа кода. Следите за новостями.
Возвращаемся к V8031. Это диагностика для Go анализатора. Когда мы её делали, то не думали, что она сможет дать такие хорошие результаты. Даже предполагали, что она может ничего не найти. Возникали мысли: "А вдруг это надуманная ошибка, которую отыщут на любом ревью или любым тестом?" Но по опыту знали, что есть десятки таких типов ошибок, которые вроде бы должны легко находиться, но их пропускают из-за лени, невнимательности или усталости.
Реализовать диагностику, естественно, было легко. С точки зрения реализации она сделана на чистом AST и по минимуму использует наши наработки сверх того, что предоставляют встроенные средства для анализа в Go. Кстати, если захотите, то можете сделать свои собственные диагностические правила. Начать можно с нашей ознакомительной статьи "Как сделать свой статический анализатор для Go?".
После реализации наступает этап тестирования. В нашей команде он состоит из трёх частей:
Сейчас нас интересует именно третий этап. Как только мы получили результаты, то сильно удивились. Давайте посмотрим на них.
Обычно в своих статьях мы расписываем ошибку и объясняем возможные пути решения. Здесь этого не будет, так как для всех фрагментов было бы одно и то же описание, которое в точности повторит описание из начала статьи.
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
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
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
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