
Проверка времен жизни и других уточняющих типов
Проверка
времени жизни и других
уточняющих типов
для Zig
Видео: https://www.youtube.com/watch?v=mf0WzTOe-40 Спонсорство: https://buymeacoffee.com/dnautics
обсуждение на hn: https://news.ycombinator.com/item?id=42923829
обсуждение на lobste.rs: https://lobste.rs/s/9sitsj/clr_checker_for_lifetimes_other
видео с демонстрацией: https://www.youtube.com/watch?v=ZY_Z-aGbYm8
Этот проект создаёт транспилятор Zig для компилятора Zig, который преобразует AIR (абстрактное промежуточное представление) в исходный код Zig, выполняющий статический анализ во время компиляции. Сгенерированный анализатор выявляет проблемы безопасности памяти, такие как использование до присваивания, использование после освобождения, утечки указателей на стек, а также специфические для Zig UB, такие как утверждения о ненулевости, нарушения размеченных объединений или неправильное использование fieldParentPtr.
Цель — обеспечить гарантии безопасности памяти на уровне Rust в Zig с помощью статического анализа AIR, без изменения самого языка.
CLR зависит от форкнутой версии компилятора Zig (включённой как подмодуль в zig/), которая добавляет поддержку маршрутизации AIR во внешние плагины. При вызове с -ofmt=air -fair-out=<plugin.so> компилятор загружает указанную разделяемую библиотеку и передаёт ей сгенерированный AIR для обработки.
CLR предназначен для продвижения программ к шаблонам жизненного цикла, которые являются явными и локально проверяемыми, а не просто для распознавания каждого технически корректного Zig-программы. Когда возможны два представления, CLR предпочитает то, которое делает состояние ресурса видимым в типе и структуре потока управления.
Например, избегайте условного закрытия не-опционального файлового дескриптора:
const file = try std.fs.cwd().openFile(path, .{});
if (should_close) {
file.close(); // Плохо: после этой ветки file неоднозначно открыт.
}
Предпочтительнее представлять условное владение с помощью опционального типа:
var file: ?std.fs.File = null;
if (should_open) {
file = try std.fs.cwd().openFile(path, .{});
}
if (file) |open_file| {
open_file.close();
}
Условное закрытие не-опционального дескриптора оставляет его жизненный цикл неоднозначным после ветки. Предполагаемая политика CLR — отклонять такой шаблон, а не нести постоянное состояние «возможно закрыто».
Тот же принцип применим к выделенным указателям. Не освобождайте память через производный указатель:
const allocation = try allocator.alloc(u8, size);
const payload = allocation[header_size..];
allocator.free(payload); // Плохо: payload — это не базовый адрес выделения.
Держите указатель на базовый адрес выделения доступным для освобождения, а производные указатели используйте только для доступа:
const allocation = try allocator.alloc(u8, size);
defer allocator.free(allocation);
const payload = allocation[header_size..];
use(payload);
Освобождение указателя на поле, подмассива или указателя, полученного арифметикой, отклоняется, если документированное внутреннее правило не восстанавливает происхождение от базового выделения.
Эти политики по умолчанию строги, поскольку они создают код с более простыми и легко проверяемыми жизненными циклами ресурсов. В будущем механизм unsafe аннотаций позволит отдельным GID или операциям отказываться от отдельных анализов. Это поддержит код, который намеренно принимает более слабые проверки в обмен на производительность, не ослабляя модель по умолчанию для остальной программы.
Это активная переработка исходного прототипа на Elixir в Zig. Реализация на Zig загружается как плагин компилятора и анализирует AIR напрямую.
На данный момент реализовано:
std.mem.Allocator:
create/destroy — выделение одного элементаalloc/free — выделение среза (включая alignedAlloc, allocSentinel и т.д.)realloc/remap — перевыделение среза с отслеживанием освобождения старого срезаdupe/dupeZ — дублирование срезаinit/deinit/allocator — полный жизненный цикл ареныstd.process.args,
std.mem.asBytes, std.HashMap, и API аллокаторов/файловstd.HashMap с канонической идентичностью метаданных/ключей/значений
при put, get, getPtr и итерации по значениямposix.open/close/dup/dup2/socket/accept/epoll_create/pipeЗапланировано (подробнее см. LIMITATIONS.md):