LINUX.ORG.RU

В Rust borrow checker обнаружены ошибки, существовавшие 11 лет и позволяющие обращаться к освобождённой памяти

 , ,


1

7

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

В качестве примера кода, демонстрирующего ошибку, приведён (ссылка на Rust playground; для запуска нужно нажать кнопку “Run”):

struct Q<T, F: FnMut(&T)>(Option<T>, F);

impl<T, F: FnMut(&T)> Drop for Q<T, F> {
    fn drop(&mut self) {
        self.1(self.0.as_ref().unwrap())
    }
}

fn main() {
    let mut out: Option<&'static _> = None;
    {
        let l = vec![vec![1]];
        let mut q = Q(None, |s| {
            out = Some(*s);
        });
        q.0 = Some(&l);
    }
    println!("{:?}", out);
}

Обнаружение unsoundness (ошибок, приводящих к нарушению гарантий компилятора) в borrow checker’е Rust — не самое необычное явление. Но эта ошибка примечательна тем, насколько простым может быть код, вызывающий её. Для других ошибок приходилось сильно изворачиваться, чтобы избежать проверок компилятора.

Ссылка на issue: https://github.com/rust-lang/rust/issues/164250

★★

Последнее исправление: shdown (всего исправлений: 2)
Ответ на: комментарий от Gordon

Так потому и стало тяжело, что они описывают новые понятия. Именно поэтому их сишники не понимают и на синтаксис ругаются.

bbc69
()
Ответ на: комментарий от vbr

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

Уже можно, клауды и прочие опенэйай сделают это возможным. Мне кажется Сишке не долго жить осталось.

Gordon
()
Ответ на: комментарий от Bircoph

Так тут кажется не проблема Оберона, а разработчики не проверили такой кейс, или ошибочно посчитали его невозможным?

Gordon
()
Ответ на: комментарий от Bircoph

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

Разработчики раста такого не обещали.

Gordon
()

хоспидя, какой же лютый синтаксис! Даже в С++ (мать его за ногу!) можно хоть как то понять, что делает программа, не зная сам С++. Здесь же сплошные закорючки, кавычки, подчеркивания. Пздц.

SpaceRanger ★★★★
()
Ответ на: комментарий от Gordon

Сишке не долго жить осталось

Сишка не умрет никогда, так как это единственный язык у которого stable ABI. Других языков попросту нет. Чтобы склеить программу, разные части которой написаны на трех языках, С++, Python и Rust, ничего кроме C ABI не подойдет.

nullb0t ★
()

Упростил пример.

struct Q<T, F: FnMut(&T)>(T, F);

impl<T, F: FnMut(&T)> Drop for Q<T, F> {
    fn drop(&mut self) {
        self.1(&self.0)
    }
}

fn main() {
    let mut v = &vec![vec![0]];
    Q(v, |&i| v = i).0 = &v.clone();
    println!("{v:?}");
}
numas13 ★
()
Ответ на: комментарий от Gordon

Я вот такой код могу читать, а на тех кто не может, как-то плевать

Хорошее дело для опенсорса

pihter ★★★★★
()
Ответ на: комментарий от Gordon

Уже можно, клауды и прочие опенэйай сделают это возможным.

Я сам не пробовал, но говорят, что на экзотических языках ИИ пишет гораздо хуже, чем на популярных. Собственно оно и понятно, это же статистическая машина в первую очередь. На чём обучалась, то и умеет. Остальное только «по аналогии» в лучшем случае.

Мне кажется Сишке не долго жить осталось.

Ну… Жить-то она будет ещё долго. Но в новых проектах как будто уже Rust кажется почти обязательным. Думаю, что обосновать, почему C а не Rust, будет не очень просто.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от Gordon

Пока это всё сказочки, без реальных поползновений. А значит реальность такова, что С живее всех живых.

nullb0t ★
()
Ответ на: комментарий от vbr

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

Всё верно. Поэтому надо делать не свой язык с нуля, а диалект Си, который с одной стороны сможет компилировать обычный Си-код и линковаться с ним бесшовно, а с другой - иметь дополнительные фичи. Так и ты сможешь его использовать не лишаясь сишных наработок, и остальные будут чуть препятствий иметь к тому чтоб его попробовать.

firkax ★★★★★
()
Ответ на: комментарий от vbr

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

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

firkax ★★★★★
()
Ответ на: комментарий от firkax

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

Это вы прям историю C++ рассказали.

Фокус в том, что так много народу осталось недовольно, что целый Rust запилили на замену и Си, и C++.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

firkax ★★★★★
()
Ответ на: комментарий от firkax

Ну да, наличие С++ не мешает сделать ещё 10 других диалектов.

Пример C++ говорит о том, что для хорошей совместимости с Си придется поддерживать косяки Си. А это уже мало кому нужно.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Эм, нет. У тебя какие-то заблуждения. Возможно ты считаешь косяками то, что ими не является, или путаешь сознательное решение авторов С++ по какому-то вопросу с «были вынуждены».

firkax ★★★★★
()
Ответ на: комментарий от firkax

Возможно ты считаешь косяками то, что ими не является, или путаешь сознательное решение авторов С++ по какому-то вопросу с «были вынуждены».

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

Просто ничего не понимаю, а то, немногое, что понимаю, путаю.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

uint8 a=100, b=3;
printf("%d", a*b);
тебе напишет не 300, а 44. Поэтому таких опций никто не сделал.

С массивами примерно то же самое, но мне лень расписывать.

firkax ★★★★★
()
Ответ на: комментарий от eao197

Стокгольмский синдром? Или вы просто самый умный?

"str" имеет тип char[4]. Вам нужно будет писать puts(&"str"[0]); вместо puts("str");.

Можно подставить какие-то костыли для этого случая, но всё равно во всех остальных случаях вам нужно будет писать &arr[0] вместо arr.

А что там с приведениями типов, хорошо видно на примере Rust: вместо a - b нужно писать что-то вроде ((a as isize) - (b as isize)) as usize. А вместо A * B / C… мда.

shdown ★★
() автор топика
Последнее исправление: shdown (всего исправлений: 2)
Ответ на: комментарий от shdown

«str» имеет тип char[4]

Вообще-то в C++ он имеет тип const char[4].

Ну и в C++ не было бы никаких проблем сделать перегрузку puts в виде шаблона:

template<typename Ch, std::size_t N>
void
puts(const Ch (&str)[N]) {...}

А что там с приведениями типов, хорошо видно на примере Rust

Из того, что в Rust маятник коснулся сильно в другую сторону, не значит, что приведение типов в Сях сделано хорошо. Можно спросить 100 сишников/плюсовиков про integer promotion и большая половина не сможет внятно рассказать что это такое, а 1/4, возможно даже и не слышала такой термин.

eao197 ★★★★★
()
Ответ на: комментарий от shdown

нужно будет писать puts(&«str»[0]);

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

во всех остальных случаях вам нужно будет писать &arr[0] вместо arr.

Это в каких? Когда вместо массива зачем-то передают указатель на массив?

вместо a - b нужно писать что-то вроде ((a as isize) - (b as isize)) as usize

Что нужно писать, зависит от того, что ты хочешь получить. А не «напишу как короче, авось прокатит», как принято в Си.

И что мешает написать (a as usize) - (b as usize)? А ещё лучше (a as usize).wrapping_sub(b as usize). Ну или какой другой sub по потребности.

unC0Rr ★★★★★
()
Ответ на: комментарий от eao197

Ну и в C++ не было бы никаких проблем сделать перегрузку puts в виде шаблона:

А как с сишными библиотеками-то это как будет работать?

shdown ★★
() автор топика
Ответ на: комментарий от unC0Rr

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

— В Си должно быть вот так!
— Зачем?
— Чтобы никто не писал на Си и все пришли в нашу секту!

Это в каких? Когда вместо массива зачем-то передают указатель на массив?

double calc_sum(const double *data, size_t n);

double A[64];
// ...fill A...
double sum = calc_sum(A, 64); // not '&A[0]'
size_t calc_total_len(const char *const *strs);

const char *array[] = { "foo", "bar", "baz", "quiz", NULL };
size_t total_len = calc_total_len(array); // not '&array[0]'
char a[32];
char b[32];
// ...fill a and b...
int rc = memcmp(a, b, 32); // not '&a[0], &b[0]'

И что мешает написать (a as usize) - (b as usize)?

То, что я хочу получить SIZE_MAX, если a=0, b=1. Ну вообще там последний каст мне нужен был на самом деле не к usize, а к i32.

(a as usize).wrapping_sub(b as usize)

Охренеть, на один сивмол короче, чем ((a as isize) - (b as isize)) as usize! И на 33 символа длиннее a - b! И ведь

E = (m as f64).wrapped_mul(C.checked_mul(C)? as f64) as f64;
так же понятно человеку, как E=mc²!

shdown ★★
() автор топика
Последнее исправление: shdown (всего исправлений: 5)
Ответ на: комментарий от vbr

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

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

Gordon
()
Ответ на: комментарий от shdown

А как с сишными библиотеками-то это как будет работать?

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

Конкретно тезис тов.firkax о том, что нужен язык, сильно совместимый с Си, но расширяющий Си новыми фичами, можно было бы всерьез обсуждать только если бы C++ не было бы. А поскольку C++ есть и любой желающий может увидеть, к чему это привело, то разговаривать, особо-то и не о чем.

C++ дал пользователям столько, что многие (включая меня) были освобождены от необходимости использовать Си. Но, из-за этой самой совместимости с Си, в нем столько возможностей отстрелить конечности, что я прекрасно понимаю всех, кто топит за Rust.

И вот видя перед глазами такой пример как C++ всерьез заявлять «надо делать не свой язык с нуля, а диалект Си, который с одной стороны сможет компилировать обычный Си-код и линковаться с ним бесшовно, а с другой - иметь дополнительные фичи.» – ну как-то странно.

eao197 ★★★★★
()
Ответ на: комментарий от shdown

В Си должно быть вот так!

Straw man, я этого не говорил.

double calc_sum(const double *data, size_t n);

Может так лучше: double calc_sum(size_t n, const double data[static n])?

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

на 33 символа длиннее a - b

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

unC0Rr ★★★★★
()
Ответ на: комментарий от firkax

Поэтому надо делать

Если надо, то делай! Или не так сильно надо?

Так и ты сможешь его использовать не лишаясь сишных наработок

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

Gordon
()
Ответ на: комментарий от eao197

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

Какие шурупы? Вы говорили: «в Си плохо сделали, я сейчас от этого страдаю». Вам было показано, что по-другому оно бы было неюзабельно. Вы привели в пример какой-то дикий костыль, который очень вербозный и не работает с сишными библиотеками.

Можно было бы предположить, что вы имели в виду, что в Си изначально нужно было делать тип у литералов const char *, а не const char[N]. Но тогда бы не работали идиомы вроде

#define MAGIC "i3-ipc"
#define MAGIC_LEN (sizeof(MAGIC) - 1)

Т.е. что вы хотите сказать, как, с вашей точки зрения должно было быть в Си — непонятно.

shdown ★★
() автор топика
Ответ на: комментарий от shdown

Какие шурупы?

Более удобные, продвинутые и безопасные языки, не цепляющиеся за совместимость с Си.

Вы говорили: «в Си плохо сделали, я сейчас от этого страдаю».

Именно так. В Си сделали кучу приколов (скажем политкорректно), которая отравляет мне жизнь в C++.

А C++ как раз развивался по сценарию, который хотел бы видеть тов.firkax, а именно:

надо делать не свой язык с нуля, а диалект Си, который с одной стороны сможет компилировать обычный Си-код и линковаться с ним бесшовно, а с другой - иметь дополнительные фичи

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

с вашей точки зрения должно было быть в Си

Моя точка зрения не в том, что в Си могло бы быть как-то по другому. А в том, что:

  • в Си сделано плохо (по крайней мере с сегодняшней точки зрения);
  • оглядываться на совместимость с Си на уровне исходных текстов уже давно не обязательно.
eao197 ★★★★★
()
Ответ на: комментарий от unC0Rr

Straw man, я этого не говорил.

Ну вы же начали кривляться в ответ на Вам нужно будет писать puts(&"str"[0]); вместо puts("str");, подразумевая это.

Может так лучше: double calc_sum(size_t n, const double data[static n])?

Функция с такой сигнатурой всё равно будет иметь тип

double ()(size_t,  const double *)
, а предупреждение будет только если компилятор допёр до статического вывода размера объекта в месте вызова. Т.е. с массивами всё будет точно так же, нужно будет передавать &arr[0]. А [static n], таким образом, играет роль просто подсказки, которая ни к чему не обязывает и с точки зрения семантики эквивалентна []. К чему вы это — непонятно.

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

Как вы выразите, что n должно быть степенью двойки, а функция считает сумму первых log_2(n) элементов?

shdown ★★
() автор топика
Последнее исправление: shdown (всего исправлений: 2)
Ответ на: комментарий от eao197

Моя точка зрения не в том, что в Си могло бы быть как-то по другому. А в том, что:

в Си сделано плохо (по крайней мере с сегодняшней точки зрения);

Нет, «плохо» означает, что можно было бы сделать лучше. Если лучше сделать нельзя — это неправильно называть «плохо».

shdown ★★
() автор топика
Последнее исправление: shdown (всего исправлений: 1)
Ответ на: комментарий от shdown

Ну вы же начали кривляться

Кривляетесь тут только вы, прямо сейчас

подразумевая это.

О, телепаты в чатике. Си уже не поменять, и я никогда не предлагаю ничего в нём менять.

Как вы выразите, что n должно быть степенью двойки, а функция считает сумму первых log_2(n) элементов?

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

unC0Rr ★★★★★
()
Ответ на: комментарий от unC0Rr

В Си - никак.

А в каком языке это можно сделать? В C++ можно? В Rust можно?

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

Нет, но [static n] это ерунда по типу __attribute__((counted_by(n))) — оно не работает в большинстве случаев, а заморачиваться нужно сильно.

shdown ★★
() автор топика
Ответ на: комментарий от unC0Rr

А не «напишу как короче, авось прокатит», как принято в Си.

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

firkax ★★★★★
()
Ответ на: комментарий от shdown

Нет, «плохо» означает, что можно было бы сделать лучше.

Можно было. В Pascal, Ada, Modula-2 же сделали иначе.

Если лучше сделать нельзя — это неправильно называть «плохо».

Тут бы нужно различать объективные факторы и субъективные.

Объективный фактор – это, например, борьба с переполнением. Ну вот переполнение знакового int-а в Си – это не просто так, а под флагом максимальной эффективности. Тут вопросов как бы и нет.

А вот неявное преобразование от char-а к int-у… Или имя Си-шного массива – это сразу указатель на его содержимое… Вот это уже субъективизм и дело вкуса Кернигана и Ричи. По тем временам им казалось норм.

Времена изменились. Сейчас, по опыту набитых шишек, уже не норм.

Ну и опять же: я не обсуждаю можно ли было сделать в Си иначе. А подчеркиваю то, что если в каком-то условном новом языке постараются сохранить совместимость с Си на уровне исходников, то это приведет к тому, что в новом языке останутся Си-шные грабли. А то и усилятся, как это можно увидеть в C++.

eao197 ★★★★★
()
Ответ на: комментарий от Gordon

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

Раст это, конечно, никакая не экзотика, а очень даже мейнстрим.

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

  1. Как уже сказал - это не экзотика. Корпус текстов на расте огромен, т.е. ИИ его знает очень хорошо.
  2. На расте в до-ИИ эпоху писать в основном программисты уровнем сильно выше среднего. Т.е. если ИИ обучался на этом коде, он обучился хорошему. Про JS, например, такого сказать нельзя.
  3. Раст это очень строгий язык. По некоторым оценкам он уровня как хаскель (с поправкой на парадигму). Т.е. если код скомпилировался, это уже убирает кучу багов, которые не надо искать. Причём, что очень важно - включая ошибки при работе с памятью и ошибки при работе с многопоточностью.
  4. У раста очень хороший компилятор. Он выдаёт очень полезные ошибки. Это позволяет ИИ очень хорошо итерироваться и исправлять ошибки.
  5. Результат работы на раст очень хорош в плане производительности. Кушает мало памяти, работает быстро.
  6. Раст достаточно высокоуровневый и в то же время достаточно низкоуровневый язык. На нём пишут ядро линукса, движок браузера и менеджер паролей. По сути его можно считать венцом эволюции. Он может заменить все языки программирования и мир от этого только выиграет. 5 лет назад я бы сказал, что «заменить-то можно, но где вы найдёте столько дешёвых программистов на Rust, ведь кода-то всё равно будет больше чем на питоне». Но сегодня уже понятно, где найдём.
vbr ★★★★★
()
Ответ на: комментарий от firkax

часто оказывается правильным

Поэтому короткие программы на Си часто работают корректно. Проблема, когда мы пишем что-то большое. И короткий вариант не показывает разницу между «так и задумано» и «как-то так вроде, потом разберёмся, если что».

unC0Rr ★★★★★
()
Ответ на: комментарий от firkax

Я тоже не понял.

С другой стороны, я думал, что этот шаблон хотя бы принимает и const char [N], и const char *. А оказывается даже puts_tpl(argv[0]) не компилируется.

shdown ★★
() автор топика
Ответ на: комментарий от eao197

Да нет, просто кто-то не до конца понимает что такое шаблоны. Впрочем, если конкретно в том примере сделать хотя бы -O1 то он заинлайнит эту функцию и всё станет норм. Но с более сложной инлайна может и не случиться, и будет у тебя сотня инстанцирований одного и того же кода. Хотя допускаю что там где-то есть оптимизатор, который склеит puts(s[10]) и puts(s[20]) если их ассемблерный код строго совпадает. Но тогда возникнет следующий вопрос: если у них строго одинаковый код, значит от N ничего не зависит, и зачем его тогда указывать в прототипе?

firkax ★★★★★
()
Ответ на: комментарий от shdown

Там для puts([4]) и puts([5]) отдельные функции скомпилировались, и для каждой другой длины тоже будет своя. Шаблоны это аналог препроцессора с параметрами, только интегрированного в основной компилятор.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария