SIGSEGV or UB in Safe Code?

В rustc 1.15.1 столкнулся с сегментацией, либо непонятным(неопределенным) поведением в зависимости от типа сборки. Прошу прощения, не понял как включить нормальное форматирование кода. Вот он:

struct Struct;

// do it with warnings
//impl Into<i64> for Struct {
//    fn into(self) -> i64 {
//        self.into()
//    }
//}

// do it without warnings
impl From<Struct> for i64 {
    fn from(v: Struct) -> i64 {
        v.into()
    }
} 

// rust version, build type, channel may change behavior (may be UB?)
fn main() {
    let i: i64 = Struct.into();
    println!("{}", i);
}

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

Баг ли?

1 лайк

Добрый день. Очень интересный баг, я был в восторге, когда увидел. Пока что этот способ лидирует в моем личном списке “как сломать компилятор максимально незаметным способом”.

Если вы написали этот код не намеренно, то я объясню. Типажи From и Into, служащие для конвертации разных типов между собой, очень тесно связаны. Любой тип, реализующий From<SomeType> автоматически создает соответсвующую реализацию Into<SomeType>. Таким образом, реализации From достаточно в 99% случаев. Into рекомендуется реализовывать вручную только в исключительных случаях.

Если взглянуть на исходники стандартной библиотеки, можно обнаружить вот такой фрагмент:

// From implies Into
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> Into<U> for T where U: From<T>
{
    fn into(self) -> U {
        U::from(self)
    }
}

Таким образом, приведенный вами код создает простейшую рекурсию из взаимных вызовов From::from и Into::into. Что особенно смешно, компилятор даже не способен об этом предупредить, потому что названия методов формально не совпадают.

Различное поведение на разных версиях компилятора, разных каналах и в разных режимах сборки объясняется, очевидно, различными оптимизациями, которые применяет LLVM. Объяснить это поведение без тщательного анализа довольно сложно. И да, я легко получаю segmentation fault в песочнице на стабильном канале в дебаг режиме.

Сложно назвать это багом компилятора, с его стороны тут никаких проблем нет. Не знаю, можно ли назвать это UB.

4 лайка

Желая понять это, начал править код примеров. Моя ошибка в типе аргумента привела к рекурсии, а она к сигментации. Тут более менее понятно.

Есть ощущение, что в новых версиях сегментацию пофиксили - да здраствует stack overflow.

Однако в release иногда программа отрабатывает, но дает разные результаты в зависимости от сборки компилятора.

Замена результата бесконечной рекурсии числом(различным) при оптимизации выглядит как UB.
Вне зависимости от того породил ли это llvm.
Впрочем, это лишь моё мнение на данный счет.

vitvakatu, благодарю за объяснение From/Into. Я это знал, но всё равно приятно. Похоже это то, что называется доброжелательностью сообщества rust :).

1 лайк