С точки зрения semanti c, в какой момент в Rust произошло неопределенное поведение noalias `& mut`? - PullRequest
3 голосов
/ 28 мая 2020

Как сказано в справочном документе Rust

Нарушение правил псевдонима указателя. & mut T и & T следуют модели noalias с ограниченной областью действия LLVM, за исключением случаев, когда & T содержит UnsafeCell.

Это действительно неоднозначно.
Я хочу знать, в какой именно момент неопределенное поведение &mut noalias возникла в Rust.

Это что-то из нижеперечисленного или что-то еще?

  1. При определении двух &mut, указывающих на один и тот же адрес?
  2. Когда два &mut, которые указывают на один и тот же адрес, подвержены ржавчине?
  3. При выполнении любой операции с &mut, которые указывают на тот же адрес, что и любой другой &mut?

Например, этот код обычно является UB:

unsafe {
    let mut x = 123usize;
    let a = (&mut x as *mut usize).as_mut().unwrap(); // created, but not accessed
    let b = (&mut x as *mut usize).as_mut().unwrap(); // created, accessed
    *b = 666;
    drop(a);
}

Но что, если я изменю код как этот :

struct S<'a> {
    ref_x: &'a mut usize
}

fn main() {
    let mut x = 123;
    let s = S { ref_x: &mut x }; // like the `T` in `ManuallyDrop<T>`
    let taken = unsafe { std::ptr::read(&s as *const S) }; // like `ManuallyDrop<T>::take`
    // at thist ime, we have two `&mut x`
    *(taken.ref_x) = 666;
    drop(s);
    // UB or not?
}

Вторая версия также является UB?
Вторая версия полностью аналогична реализации std :: mem :: ManuallyDrop . Если вторая версия - UB, это ошибка безопасности std::mem::ManuallyDrop<T>?

1 Ответ

1 голос
/ 29 мая 2020

Чем не является ограничение псевдонима

На самом деле обычно несколько существующих &mut T псевдонимов одного и того же элемента.

Самый простой пример:

fn main() {
   let mut i = 32;
   let j = &mut i;
   let k = &mut *j;

   *k = 3;

   println!("{}", i);
}

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

Если вы посмотрите на реализацию ManuallyDrop::take:

pub unsafe fn take(slot: &mut ManuallyDrop<T>) -> T {
    ptr::read(&slot.value)
}

Вы заметите, что нет одновременно доступных &mut T: вызов функции повторно заимствует ManuallyDrop делает slot единственная доступная изменяемая ссылка.

Почему алиасинг в Rust так плохо определен

Это действительно неоднозначно. Я хочу знать, в какой именно момент в Rust произошло неопределенное поведение &mut noalias.

Не повезло, потому что, как указано в Nomicon :

К сожалению, Rust на самом деле не определил свою модель алиасинга.

Причина в том, что языковая группа хочет убедиться, что определение, которое они достигают, является как безопасным (очевидно, так), практическим , и все же не закрыть дверь до возможных доработок. Это сложная задача.

Рабочая группа Rust Unsafe Code Guidelines все еще работает над установлением точных границ, и, в частности, Ральф Юнг работает над операционной моделью псевдонима под названием Stacked Borrows .

Примечание. Модель Stacked Borrows реализована в MIRI, поэтому вы можете проверить свой код на соответствие модели Stacked Borrows, просто выполнив свой код в MIRI. Конечно, Stacked Borrows все еще экспериментальный, так что это ничего не гарантирует.

Что рекомендует предостережение

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

Таким образом, я интерпретирую правило без наложения имен из &mut T как:

В любой точке кода в области не должно быть двух доступных ссылок, которые псевдонимы одной и той же памяти, если одна из них - &mut T.

То есть, я считаю, что формирует a &mut T в экземпляр T, для которого другой &T или &mut T находится в области без аннулирования псевдонимов (через заимствование) неправильно сформирован.

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

...