Skip to content

Commit b024de1

Browse files
committed
Auto merge of #2785 - Vanille-N:tree-borrows-merge, r=RalfJung
Tree borrows This PR implements the experimental Tree Borrows (TB) rules for tracking reference aliasing, as an optional alternative to Stacked Borrows (SB). SB and TB are mutually exclusive. Using `-Zmiri-tree-borrows` replaces every invocation of SB with the equivalent TB procedure. A detailed explanation of the TB rules is currently under review, you can find the latest version [here [work in progress]](https://github.com/Vanille-N/tree-borrows/blob/master/model/treebor.pdf). This PR does NOT yet include - enough `fail` tests for TB (although TB is less reliant than SB on `fail` tests to check that the implementation matches the design due to `pass` tests being more strict) - good diagnostics for TB violations
2 parents 3ad2fec + 782b869 commit b024de1

File tree

98 files changed

+3641
-140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+3641
-140
lines changed

src/tools/miri/README.md

+13-118
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ for example:
1515
or an invalid enum discriminant)
1616
* **Experimental**: Violations of the [Stacked Borrows] rules governing aliasing
1717
for reference types
18+
* **Experimental**: Violations of the Tree Borrows aliasing rules, as an optional
19+
alternative to [Stacked Borrows]
1820
* **Experimental**: Data races
1921

2022
On top of that, Miri will also tell you about memory leaks: when there is memory
@@ -357,9 +359,11 @@ to Miri failing to detect cases of undefined behavior in a program.
357359
* `-Zmiri-disable-data-race-detector` disables checking for data races. Using
358360
this flag is **unsound**. This implies `-Zmiri-disable-weak-memory-emulation`.
359361
* `-Zmiri-disable-stacked-borrows` disables checking the experimental
360-
[Stacked Borrows] aliasing rules. This can make Miri run faster, but it also
361-
means no aliasing violations will be detected. Using this flag is **unsound**
362-
(but the affected soundness rules are experimental).
362+
aliasing rules to track borrows ([Stacked Borrows] and Tree Borrows).
363+
This can make Miri run faster, but it also means no aliasing violations will
364+
be detected. Using this flag is **unsound** (but the affected soundness rules
365+
are experimental). Later flags take precedence: borrow tracking can be reactivated
366+
by `-Zmiri-tree-borrows`.
363367
* `-Zmiri-disable-validation` disables enforcing validity invariants, which are
364368
enforced by default. This is mostly useful to focus on other failures (such
365369
as out-of-bounds accesses) first. Setting this flag means Miri can miss bugs
@@ -421,6 +425,9 @@ to Miri failing to detect cases of undefined behavior in a program.
421425
* `-Zmiri-track-weak-memory-loads` shows a backtrace when weak memory emulation returns an outdated
422426
value from a load. This can help diagnose problems that disappear under
423427
`-Zmiri-disable-weak-memory-emulation`.
428+
* `-Zmiri-tree-borrows` replaces [Stacked Borrows] with the Tree Borrows rules.
429+
The soundness rules are already experimental without this flag, but even more
430+
so with this flag.
424431
* `-Zmiri-force-page-size=<num>` overrides the default page size for an architecture, in multiples of 1k.
425432
`4` is default for most targets. This value should always be a power of 2 and nonzero.
426433

@@ -435,7 +442,7 @@ Some native rustc `-Z` flags are also very relevant for Miri:
435442
functions. This is needed so that Miri can execute such functions, so Miri
436443
sets this flag per default.
437444
* `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri
438-
enables this per default because it is needed for [Stacked Borrows].
445+
enables this per default because it is needed for [Stacked Borrows] and Tree Borrows.
439446

440447
Moreover, Miri recognizes some environment variables:
441448

@@ -501,120 +508,8 @@ binaries, and as such worth documenting:
501508
## Miri `extern` functions
502509

503510
Miri provides some `extern` functions that programs can import to access
504-
Miri-specific functionality:
505-
506-
```rust
507-
#[cfg(miri)]
508-
extern "Rust" {
509-
/// Miri-provided extern function to mark the block `ptr` points to as a "root"
510-
/// for some static memory. This memory and everything reachable by it is not
511-
/// considered leaking even if it still exists when the program terminates.
512-
///
513-
/// `ptr` has to point to the beginning of an allocated block.
514-
fn miri_static_root(ptr: *const u8);
515-
516-
// Miri-provided extern function to get the amount of frames in the current backtrace.
517-
// The `flags` argument must be `0`.
518-
fn miri_backtrace_size(flags: u64) -> usize;
519-
520-
/// Miri-provided extern function to obtain a backtrace of the current call stack.
521-
/// This writes a slice of pointers into `buf` - each pointer is an opaque value
522-
/// that is only useful when passed to `miri_resolve_frame`.
523-
/// `buf` must have `miri_backtrace_size(0) * pointer_size` bytes of space.
524-
/// The `flags` argument must be `1`.
525-
fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
526-
527-
/// Miri-provided extern function to resolve a frame pointer obtained
528-
/// from `miri_get_backtrace`. The `flags` argument must be `1`,
529-
/// and `MiriFrame` should be declared as follows:
530-
///
531-
/// ```rust
532-
/// #[repr(C)]
533-
/// struct MiriFrame {
534-
/// // The size of the name of the function being executed, encoded in UTF-8
535-
/// name_len: usize,
536-
/// // The size of filename of the function being executed, encoded in UTF-8
537-
/// filename_len: usize,
538-
/// // The line number currently being executed in `filename`, starting from '1'.
539-
/// lineno: u32,
540-
/// // The column number currently being executed in `filename`, starting from '1'.
541-
/// colno: u32,
542-
/// // The function pointer to the function currently being executed.
543-
/// // This can be compared against function pointers obtained by
544-
/// // casting a function (e.g. `my_fn as *mut ()`)
545-
/// fn_ptr: *mut ()
546-
/// }
547-
/// ```
548-
///
549-
/// The fields must be declared in exactly the same order as they appear in `MiriFrame` above.
550-
/// This function can be called on any thread (not just the one which obtained `frame`).
551-
fn miri_resolve_frame(frame: *mut (), flags: u64) -> MiriFrame;
552-
553-
/// Miri-provided extern function to get the name and filename of the frame provided by `miri_resolve_frame`.
554-
/// `name_buf` and `filename_buf` should be allocated with the `name_len` and `filename_len` fields of `MiriFrame`.
555-
/// The flags argument must be `0`.
556-
fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
557-
558-
/// Miri-provided extern function to begin unwinding with the given payload.
559-
///
560-
/// This is internal and unstable and should not be used; we give it here
561-
/// just to be complete.
562-
fn miri_start_panic(payload: *mut u8) -> !;
563-
564-
/// Miri-provided extern function to get the internal unique identifier for the allocation that a pointer
565-
/// points to. If this pointer is invalid (not pointing to an allocation), interpretation will abort.
566-
///
567-
/// This is only useful as an input to `miri_print_borrow_stacks`, and it is a separate call because
568-
/// getting a pointer to an allocation at runtime can change the borrow stacks in the allocation.
569-
/// This function should be considered unstable. It exists only to support `miri_print_borrow_stacks` and so
570-
/// inherits all of its instability.
571-
fn miri_get_alloc_id(ptr: *const ()) -> u64;
572-
573-
/// Miri-provided extern function to print (from the interpreter, not the program) the contents of all
574-
/// borrow stacks in an allocation. The leftmost tag is the bottom of the stack.
575-
/// The format of what this emits is unstable and may change at any time. In particular, users should be
576-
/// aware that Miri will periodically attempt to garbage collect the contents of all stacks. Callers of
577-
/// this function may wish to pass `-Zmiri-tag-gc=0` to disable the GC.
578-
///
579-
/// This function is extremely unstable. At any time the format of its output may change, its signature may
580-
/// change, or it may be removed entirely.
581-
fn miri_print_borrow_stacks(alloc_id: u64);
582-
583-
/// Miri-provided extern function to print (from the interpreter, not the
584-
/// program) the contents of a section of program memory, as bytes. Bytes
585-
/// written using this function will emerge from the interpreter's stdout.
586-
fn miri_write_to_stdout(bytes: &[u8]);
587-
588-
/// Miri-provided extern function to print (from the interpreter, not the
589-
/// program) the contents of a section of program memory, as bytes. Bytes
590-
/// written using this function will emerge from the interpreter's stderr.
591-
fn miri_write_to_stderr(bytes: &[u8]);
592-
593-
/// Miri-provided extern function to allocate memory from the interpreter.
594-
///
595-
/// This is useful when no fundamental way of allocating memory is
596-
/// available, e.g. when using `no_std` + `alloc`.
597-
fn miri_alloc(size: usize, align: usize) -> *mut u8;
598-
599-
/// Miri-provided extern function to deallocate memory.
600-
fn miri_dealloc(ptr: *mut u8, size: usize, align: usize);
601-
602-
/// Convert a path from the host Miri runs on to the target Miri interprets.
603-
/// Performs conversion of path separators as needed.
604-
///
605-
/// Usually Miri performs this kind of conversion automatically. However, manual conversion
606-
/// might be necessary when reading an environment variable that was set on the host
607-
/// (such as TMPDIR) and using it as a target path.
608-
///
609-
/// Only works with isolation disabled.
610-
///
611-
/// `in` must point to a null-terminated string, and will be read as the input host path.
612-
/// `out` must point to at least `out_size` many bytes, and the result will be stored there
613-
/// with a null terminator.
614-
/// Returns 0 if the `out` buffer was large enough, and the required size otherwise.
615-
fn miri_host_to_target_path(path: *const std::ffi::c_char, out: *mut std::ffi::c_char, out_size: usize) -> usize;
616-
}
617-
```
511+
Miri-specific functionality. They are declared in
512+
[/tests/utils/miri\_extern.rs](/tests/utils/miri_extern.rs).
618513

619514
## Contributing and getting help
620515

src/tools/miri/src/bin/miri.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use rustc_middle::{
3232
};
3333
use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace};
3434

35-
use miri::{BacktraceStyle, ProvenanceMode, RetagFields};
35+
use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields};
3636

3737
struct MiriCompilerCalls {
3838
miri_config: miri::MiriConfig,
@@ -317,6 +317,8 @@ fn main() {
317317
miri_config.validate = false;
318318
} else if arg == "-Zmiri-disable-stacked-borrows" {
319319
miri_config.borrow_tracker = None;
320+
} else if arg == "-Zmiri-tree-borrows" {
321+
miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows);
320322
} else if arg == "-Zmiri-disable-data-race-detector" {
321323
miri_config.data_race_detector = false;
322324
miri_config.weak_memory_emulation = false;

src/tools/miri/src/borrow_tracker/mod.rs

+69-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_target::abi::Size;
1111

1212
use crate::*;
1313
pub mod stacked_borrows;
14+
pub mod tree_borrows;
1415

1516
pub type CallId = NonZeroU64;
1617

@@ -230,8 +231,10 @@ impl GlobalStateInner {
230231
/// Which borrow tracking method to use
231232
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
232233
pub enum BorrowTrackerMethod {
233-
/// Stacked Borrows, as implemented in borrow_tracker/stacked
234+
/// Stacked Borrows, as implemented in borrow_tracker/stacked_borrows
234235
StackedBorrows,
236+
/// Tree borrows, as implemented in borrow_tracker/tree_borrows
237+
TreeBorrows,
235238
}
236239

237240
impl BorrowTrackerMethod {
@@ -258,6 +261,10 @@ impl GlobalStateInner {
258261
AllocState::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
259262
id, alloc_size, self, kind, machine,
260263
)))),
264+
BorrowTrackerMethod::TreeBorrows =>
265+
AllocState::TreeBorrows(Box::new(RefCell::new(Tree::new_allocation(
266+
id, alloc_size, self, kind, machine,
267+
)))),
261268
}
262269
}
263270
}
@@ -273,6 +280,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
273280
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
274281
match method {
275282
BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val),
283+
BorrowTrackerMethod::TreeBorrows => this.tb_retag_ptr_value(kind, val),
276284
}
277285
}
278286

@@ -285,6 +293,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
285293
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
286294
match method {
287295
BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place),
296+
BorrowTrackerMethod::TreeBorrows => this.tb_retag_place_contents(kind, place),
288297
}
289298
}
290299

@@ -293,6 +302,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
293302
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
294303
match method {
295304
BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
305+
BorrowTrackerMethod::TreeBorrows => this.tb_retag_return_place(),
296306
}
297307
}
298308

@@ -301,6 +311,34 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
301311
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
302312
match method {
303313
BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
314+
BorrowTrackerMethod::TreeBorrows => this.tb_expose_tag(alloc_id, tag),
315+
}
316+
}
317+
318+
fn give_pointer_debug_name(
319+
&mut self,
320+
ptr: Pointer<Option<Provenance>>,
321+
nth_parent: u8,
322+
name: &str,
323+
) -> InterpResult<'tcx> {
324+
let this = self.eval_context_mut();
325+
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
326+
match method {
327+
BorrowTrackerMethod::StackedBorrows => {
328+
this.tcx.tcx.sess.warn("Stacked Borrows does not support named pointers; `miri_pointer_name` is a no-op");
329+
Ok(())
330+
}
331+
BorrowTrackerMethod::TreeBorrows =>
332+
this.tb_give_pointer_debug_name(ptr, nth_parent, name),
333+
}
334+
}
335+
336+
fn print_borrow_state(&mut self, alloc_id: AllocId, show_unnamed: bool) -> InterpResult<'tcx> {
337+
let this = self.eval_context_mut();
338+
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
339+
match method {
340+
BorrowTrackerMethod::StackedBorrows => this.print_stacks(alloc_id),
341+
BorrowTrackerMethod::TreeBorrows => this.print_tree(alloc_id, show_unnamed),
304342
}
305343
}
306344
}
@@ -310,6 +348,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
310348
pub enum AllocState {
311349
/// Data corresponding to Stacked Borrows
312350
StackedBorrows(Box<RefCell<stacked_borrows::AllocState>>),
351+
/// Data corresponding to Tree Borrows
352+
TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
313353
}
314354

315355
impl machine::AllocExtra {
@@ -328,6 +368,14 @@ impl machine::AllocExtra {
328368
_ => panic!("expected Stacked Borrows borrow tracking, got something else"),
329369
}
330370
}
371+
372+
#[track_caller]
373+
pub fn borrow_tracker_tb(&self) -> &RefCell<tree_borrows::AllocState> {
374+
match self.borrow_tracker {
375+
Some(AllocState::TreeBorrows(ref tb)) => tb,
376+
_ => panic!("expected Tree Borrows borrow tracking, got something else"),
377+
}
378+
}
331379
}
332380

333381
impl AllocState {
@@ -341,6 +389,14 @@ impl AllocState {
341389
match self {
342390
AllocState::StackedBorrows(sb) =>
343391
sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
392+
AllocState::TreeBorrows(tb) =>
393+
tb.borrow_mut().before_memory_access(
394+
AccessKind::Read,
395+
alloc_id,
396+
prov_extra,
397+
range,
398+
machine,
399+
),
344400
}
345401
}
346402

@@ -354,6 +410,14 @@ impl AllocState {
354410
match self {
355411
AllocState::StackedBorrows(sb) =>
356412
sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
413+
AllocState::TreeBorrows(tb) =>
414+
tb.get_mut().before_memory_access(
415+
AccessKind::Write,
416+
alloc_id,
417+
prov_extra,
418+
range,
419+
machine,
420+
),
357421
}
358422
}
359423

@@ -367,12 +431,15 @@ impl AllocState {
367431
match self {
368432
AllocState::StackedBorrows(sb) =>
369433
sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
434+
AllocState::TreeBorrows(tb) =>
435+
tb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
370436
}
371437
}
372438

373439
pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
374440
match self {
375441
AllocState::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
442+
AllocState::TreeBorrows(tb) => tb.borrow_mut().remove_unreachable_tags(tags),
376443
}
377444
}
378445
}
@@ -381,6 +448,7 @@ impl VisitTags for AllocState {
381448
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
382449
match self {
383450
AllocState::StackedBorrows(sb) => sb.visit_tags(visit),
451+
AllocState::TreeBorrows(tb) => tb.visit_tags(visit),
384452
}
385453
}
386454
}

0 commit comments

Comments
 (0)