Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
076de87
[Priroda] Add source-shaped value UI coverage
moabo3li Jul 24, 2026
bc56612
Prepare for merging from rust-lang/rust
Jul 27, 2026
0a7b19e
Merge ref 'dfbea5bc945a' from rust-lang/rust
Jul 27, 2026
a3eac7e
fmt
Jul 27, 2026
316b77b
skip weak memory buffer handling when there is only one thread
RalfJung Jul 14, 2026
83a423b
bless test-cargo-miri
RalfJung Jul 27, 2026
47dfbec
Merge pull request #5191 from RalfJung/single-thread-no-store-buffer
RalfJung Jul 27, 2026
2e6ce93
Merge pull request #5228 from rust-lang/rustup-2026-07-27
RalfJung Jul 27, 2026
47eb30f
[Priroda] Add source-shaped value renderer plumbing
moabo3li Jul 24, 2026
7edd27f
[Priroda] Render source-shaped struct values
moabo3li Jul 24, 2026
543afd7
[Priroda] Render source-shaped enum values
moabo3li Jul 24, 2026
65ee03d
[Priroda] Render source-shaped tuple values
moabo3li Jul 24, 2026
77b975c
[Priroda] Render source-shaped array and slice values
moabo3li Jul 24, 2026
925bc0e
fix ptr_from_addr_cast comment
zjp-CN Jul 28, 2026
d203782
Prepare for merging from rust-lang/rust
Jul 28, 2026
aa711f6
Merge ref 'd3ea0356830d' from rust-lang/rust
Jul 28, 2026
045757c
Merge pull request #5231 from KMiri-rs/fix-ptr_from_addr_cast-comment
RalfJung Jul 28, 2026
204ccaa
tests: libc-poll: test a more reaosnable FD number for POLLNVAL
RalfJung Jul 28, 2026
9c4c3fe
macos poll seems be kinda broken
RalfJung Jul 28, 2026
e80409a
Merge pull request #5232 from rust-lang/rustup-2026-07-28
RalfJung Jul 28, 2026
745f929
Merge pull request #5229 from moabo3li/locals-projected-names-and-typ…
oli-obk Jul 28, 2026
c10cc83
add aarch64 SHA256 intrinsics
jrakibi Jul 11, 2026
856278b
x86/sha: drop "reg" from variable names
jrakibi Jul 12, 2026
c78d212
Merge pull request #5064 from jrakibi/25-05-aarch64-sha256-intrinsics
RalfJung Jul 28, 2026
c72ad96
Prepare for merging from rust-lang/rust
Jul 29, 2026
6fc625b
Merge ref '701a6513a48e' from rust-lang/rust
Jul 29, 2026
8093103
Merge pull request #5233 from rust-lang/rustup-2026-07-29
RalfJung Jul 29, 2026
939e545
Prepare for merging from rust-lang/rust
Aug 1, 2026
660e976
Merge ref 'cb9d1b064054' from rust-lang/rust
Aug 1, 2026
d529bff
fmt
Aug 1, 2026
62def3b
Merge pull request #5236 from rust-lang/rustup-2026-08-01
RalfJung Aug 1, 2026
c9d77c9
show clear error when encountering an argfile
RalfJung Aug 1, 2026
61f9cf8
Merge pull request #5237 from RalfJung/cargo-miri-argfile
RalfJung Aug 1, 2026
b72b849
Prepare for merging from rust-lang/rust
Aug 2, 2026
c4de87e
Merge ref '73dc9167f1cd' from rust-lang/rust
Aug 2, 2026
937dfd4
fmt
Aug 2, 2026
b488a90
update genmc spinloop test output
RalfJung Aug 2, 2026
79aa9b1
Merge pull request #5238 from rust-lang/rustup-2026-08-02
RalfJung Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/tools/miri/cargo-miri/src/phases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,16 @@ pub fn phase_rustc(args: impl Iterator<Item = String>, phase: RustcPhase) {
}
}

// Ensure we show an error if we encounter an argfile, rather than randomly misbehvaing.
// We know rustdoc doesn't put anything important in the argfiles so we can ignore them there.
let args = args.inspect(|arg| {
if phase != RustcPhase::Rustdoc && arg.starts_with('@') {
show_error!(
"cargo uses an argfile to invoke rustc, which is not supported by cargo-miri"
)
}
});

let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let target_crate = is_target_crate();
Expand Down
182 changes: 178 additions & 4 deletions src/tools/miri/priroda/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ use std::path::PathBuf;

use miri::Immediate::Uninit;
use miri::{interpret, *};
use rustc_abi::Size;
use rustc_abi::{FIRST_VARIANT, FieldIdx, Size};
use rustc_driver::Compilation;
use rustc_hir::attrs::CrateType;
use rustc_hir::def::CtorKind;
use rustc_interface::interface;
use rustc_middle::mir::interpret::AllocId;
use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment};
use rustc_middle::ty::{TyCtxt, TyKind};
use rustc_middle::ty::{self, TyCtxt, TyKind};
use rustc_session::EarlyDiagCtxt;
use rustc_session::config::ErrorOutputType;
use rustc_span::source_map::SourceMap;
Expand Down Expand Up @@ -505,6 +506,179 @@ impl<'tcx> PrirodaContext<'tcx> {
interp_ok(format!("[{}]", rendered.join(" ")))
}

/// Render an evaluated operand using Rust-source-shaped containers with raw leaves.
///
/// The operand is produced from live interpreter state, usually via `local_to_op`
/// for a whole MIR local or `eval_place_to_op` for a projected debug-info place.
///
/// This intentionally does not call user `Debug` / `Display`, and it does not
/// try to make every scalar leaf pretty yet. Unsupported cases and leaf values
/// fall back to `render_op`, preserving the old raw byte/provenance renderer.
///
/// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers,
/// chars, raw pointers/references) once the source-shaped container output is
/// stable enough to stop depending on byte dumps for every field.
///
/// FIXME: decide how much dereferencing belongs in this renderer. References
/// currently stay as raw pointer leaves; following them may belong in the
/// existing `follow` command instead of automatic local rendering.
fn render_source_shaped_op(&self, op: OpTy<'tcx>) -> String {
self.render_source_shaped_op_inner(op, 0)
}

/// Recursive worker for `render_source_shaped_op`.
///
/// The depth limit keeps cyclic/reference-heavy values from making debugger
/// output explode once more container kinds are added. At the limit, the raw
/// renderer remains the ground truth.
///
/// FIXME: replace this fixed recursion limit with a value-size/output-budget
/// policy so large acyclic values and deeply nested values degrade more
/// predictably.
fn render_source_shaped_op_inner(&self, op: OpTy<'tcx>, depth: usize) -> String {
const MAX_SOURCE_SHAPE_DEPTH: usize = 8;

if depth >= MAX_SOURCE_SHAPE_DEPTH {
return self.render_op(op);
}

match op.layout.ty.kind() {
// Empty enums have no active variant to format. Unions do not record
// which field is currently active, so choosing one would be misleading.
//
// FIXME: support unions only with an explicit user-selected field or
// another source of active-field information. Guessing from layout
// bytes would make debugger output look more certain than it is.
ty::Adt(def, _) if def.variants().is_empty() || def.is_union() => self.render_op(op),

ty::Adt(def, _) => {
// Enums need their runtime discriminant and a downcasted layout
// view before fields can be projected. Structs use their sole
// variant directly. Keep the display name tied to the same choice.
let (variant_idx, down, name) = if def.is_enum() {
let variant_idx = match self.ecx.read_discriminant(&op).discard_err() {
Some(variant_idx) => variant_idx,
// FIXME: expose this as an explicit render error when
// Priroda grows structured value states. Falling back to
// bytes keeps today's UI usable but hides why the enum
// could not be source-shaped.
None => return self.render_op(op),
};
let down = match self.ecx.project_downcast(&op, variant_idx).discard_err() {
Some(down) => down,
// FIXME: distinguish invalid/uninitialized discriminants
// from projection bugs in the rendered output once locals
// can carry structured diagnostics.
None => return self.render_op(op),
};
let variant_def = &def.variants()[variant_idx];
(
variant_idx,
down,
format!("{}::{}", self.ecx.tcx.item_name(def.did()), variant_def.name),
)
} else {
let variant_idx = FIRST_VARIANT;
let variant_def = &def.variants()[variant_idx];
(variant_idx, op.clone(), variant_def.name.to_string())
};

let variant_def = &def.variants()[variant_idx];

let mut fields = Vec::with_capacity(variant_def.fields.len());
for i in 0..variant_def.fields.len() {
let field_idx = FieldIdx::from_usize(i);
// `project_field` avoids manual offset math and works for both
// immediate and memory-backed operands through `Projectable`.
let field_op = match self.ecx.project_field(&down, field_idx).discard_err() {
Some(field_op) => field_op,
// FIXME: preserve the successfully rendered fields and
// mark only this field as unavailable once the value model
// can represent partial render failures.
None => return self.render_op(op),
};
fields.push(self.render_source_shaped_op_inner(field_op, depth + 1));
}

// Match Rust constructor spelling:
// - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit`
// - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()`
// - `None`: braced structs/variants, including the empty `{}` case
match variant_def.ctor_kind() {
Some(CtorKind::Const) => name,
Some(CtorKind::Fn) => format!("{name}({})", fields.join(", ")),
None if fields.is_empty() => format!("{name} {{}}"),
None => {
let fields = variant_def
.fields
.iter()
.zip(fields)
.map(|(field_def, value)| format!("{}: {value}", field_def.name))
.collect::<Vec<_>>()
.join(", ");
format!("{name} {{ {fields} }}")
}
}
}

ty::Tuple(args) => {
let mut fields = Vec::with_capacity(args.len());
for i in 0..args.len() {
// Tuples have no field names in source, so preserve their
// source field order and render children positionally.
let field_op =
match self.ecx.project_field(&op, FieldIdx::from_usize(i)).discard_err() {
Some(field_op) => field_op,
// FIXME: render tuple fields independently so one
// projection failure does not throw away the whole
// source-shaped tuple.
None => return self.render_op(op),
};
fields.push(self.render_source_shaped_op_inner(field_op, depth + 1));
}

if fields.len() == 1 {
format!("({},)", fields[0])
} else {
format!("({})", fields.join(", "))
}
}

ty::Array(_, _) | ty::Slice(_) => {
// `project_array_fields` uses the dynamic length for slices. That
// avoids the classic mistake of treating slice layout as a fixed
// zero-length array.
let mut iter = match self.ecx.project_array_fields(&op).discard_err() {
Some(iter) => iter,
// FIXME: when slice metadata is invalid, show that as a slice
// length problem instead of silently falling back to raw bytes.
None => return self.render_op(op),
};

let mut fields = Vec::new();
// FIXME: add an output budget/truncation policy before rendering
// very large arrays or slices in full.
loop {
match iter.next(&self.ecx).discard_err() {
Some(Some((_idx, field_op))) =>
fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)),
Some(None) => break,
// FIXME: keep already-rendered elements and mark the
// failed index once partial render errors are supported.
None => return self.render_op(op),
}
}

format!("[{}]", fields.join(", "))
}

// FIXME: consider source-shaped special cases for strings, closures,
// generators/coroutines, trait objects, and SIMD/vector-like types.
// Until then these stay on the raw renderer path.
_ => self.render_op(op),
}
}

/// Render an evaluated operand using the same raw representation for
/// whole locals and projected MIR places.
fn render_op(&self, op: OpTy<'tcx>) -> String {
Expand Down Expand Up @@ -613,7 +787,7 @@ impl<'tcx> PrirodaContext<'tcx> {
.ecx
.local_to_op(local, None)
.expect("this error can only occur in CTFE on generic code");
local_desc.value = self.render_op(op);
local_desc.value = self.render_source_shaped_op(op);
}
};

Expand Down Expand Up @@ -682,7 +856,7 @@ impl<'tcx> PrirodaContext<'tcx> {
let value = self
.ecx
.eval_place_to_op(*place, None)
.map(|op| self.render_op(op))
.map(|op| self.render_source_shaped_op(op))
.unwrap_or_else(|err| {
format!("<error: {}>", interpret::format_interp_error(err))
});
Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/priroda/tests/ui/locals_access_field.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_access_field.rs:14
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
Name: extraslice, Id: _1, Ty: ExtraSlice<'_>, Value: ExtraSlice { _slice: [{ALLOC_PTR} 00 00 00 00 00 00 00 00], _extra: [00 00 00 00] }
Name: _slice, Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
Name: _extra, Id: _3, Ty: u32, Value: 0_u32
(priroda) Id: _0, Ty: (), Value: <uninit>
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: [{ALLOC_PTR} 00 00 00 00 00 00 00 00 00 00 00 00 __ __ __ __]
(priroda) Id: _1, Ty: ExtraSlice<'_>, Value: ExtraSlice { _slice: [{ALLOC_PTR} 00 00 00 00 00 00 00 00], _extra: [00 00 00 00] }
(priroda) Id: _2, Ty: &[u8], Value: (pointer to {ALLOC_PTR}, 0x0000000000000000): &[u8]
(priroda) Id: _3, Ty: u32, Value: 0_u32
(priroda) no local for this id
Expand Down
8 changes: 4 additions & 4 deletions src/tools/miri/priroda/tests/ui/locals_corpus_async.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:31
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: <none>, Id: _1, Ty: (u32, i32), Value: (0x00000005, 0x00000006): (u32, i32)
Name: <none>, Id: _1, Ty: (u32, i32), Value: (5_u32, 6_i32)
Name: first, Id: _1.0, Ty: u32, Value: 5_u32
Name: second, Id: _1.1, Ty: i32, Value: 6_i32
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:45
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: <none>, Id: _1, Ty: S, Value: {transmute(0x40a00000): S}
Name: <none>, Id: _1, Ty: S, Value: S { x: 5f32 }
Name: x, Id: _1.0, Ty: f32, Value: 5f32
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:55
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: <none>, Id: _1, Ty: std::option::Option<i32>, Value: [01 00 00 00 05 00 00 00]
Name: <none>, Id: _1, Ty: std::option::Option<i32>, Value: Option::Some([05 00 00 00])
Name: inner, Id: _1 as variant#1.0, Ty: i32, Value: [05 00 00 00]
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_corpus_async.rs:66
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: <none>, Id: _1, Ty: std::option::Option<&i32>, Value: [{ALLOC_PTR}]
Name: <none>, Id: _1, Ty: std::option::Option<&i32>, Value: Option::Some([{ALLOC_PTR}])
Name: pointer, Id: _1 as variant#1.0, Ty: &i32, Value: [{ALLOC_PTR}]
Name: deref, Id: _1 as variant#1.0.*, Ty: i32, Value: [05 00 00 00]
(priroda) Allocation alloc2+0: [05 00 00 00]
Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/priroda/tests/ui/locals_mplace_metadata.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:8
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: slice, Id: _1, Ty: [u8], Value: [01 02 03]
Name: slice, Id: _1, Ty: [u8], Value: [[01], [02], [03]]
Name: <none>, Id: _2, Ty: &[u8], Value: <dead>
Name: <none>, Id: _3, Ty: &[u8], Value: <dead>
(priroda) Id: _1, Ty: [u8], Value: [01 02 03]
(priroda) Id: _1, Ty: [u8], Value: [[01], [02], [03]]
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_mplace_metadata.rs:12
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Expand Down
16 changes: 8 additions & 8 deletions src/tools/miri/priroda/tests/ui/locals_pointer_rendering.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,37 @@
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_pointer_rendering.rs:59
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: target, Id: _1, Ty: [u8; 2], Value: [0a 14]
Name: pointer_at_offset0, Id: _2, Ty: PointerAtOffset0<'_>, Value: [{ALLOC_PTR}]
Name: target, Id: _1, Ty: [u8; 2], Value: [[0a], [14]]
Name: pointer_at_offset0, Id: _2, Ty: PointerAtOffset0<'_>, Value: PointerAtOffset0 { ptr: [{ALLOC_PTR}] }
Name: <none>, Id: _3, Ty: &u8, Value: <dead>
Name: <none>, Id: _4, Ty: &u8, Value: <dead>
Name: <none>, Id: _5, Ty: usize, Value: <dead>
Name: <none>, Id: _6, Ty: bool, Value: true
Name: pointer_after_bytes, Id: _7, Ty: PointerAfterBytes<'_>, Value: [01 02 03 __ __ __ __ __ {ALLOC_PTR}]
Name: pointer_after_bytes, Id: _7, Ty: PointerAfterBytes<'_>, Value: PointerAfterBytes { bytes: [[01], [02], [03]], ptr: [{ALLOC_PTR}] }
Name: <none>, Id: _8, Ty: [u8; 3], Value: <dead>
Name: <none>, Id: _9, Ty: &u8, Value: <dead>
Name: <none>, Id: _10, Ty: &u8, Value: <dead>
Name: <none>, Id: _11, Ty: usize, Value: <dead>
Name: <none>, Id: _12, Ty: bool, Value: true
Name: pointer_at_end, Id: _13, Ty: PointerAtEnd<'_>, Value: [04 __ __ __ __ __ __ __ {ALLOC_PTR}]
Name: pointer_at_end, Id: _13, Ty: PointerAtEnd<'_>, Value: PointerAtEnd { byte: [04], ptr: [{ALLOC_PTR}] }
Name: <none>, Id: _14, Ty: &u8, Value: <dead>
Name: <none>, Id: _15, Ty: &u8, Value: <dead>
Name: <none>, Id: _16, Ty: usize, Value: <dead>
Name: <none>, Id: _17, Ty: bool, Value: true
Name: uninit_around_pointer, Id: _18, Ty: UninitAroundPointer<'_>, Value: [__ __ __ __ __ __ __ __ {ALLOC_PTR} __ __ __ __ __ __ __ __]
Name: uninit_around_pointer, Id: _18, Ty: UninitAroundPointer<'_>, Value: UninitAroundPointer { before: [__ __], ptr: [{ALLOC_PTR}], after: [__ __] }
Name: <none>, Id: _19, Ty: std::mem::MaybeUninit<[u8; 2]>, Value: <dead>
Name: <none>, Id: _20, Ty: &u8, Value: <dead>
Name: <none>, Id: _21, Ty: &u8, Value: <dead>
Name: <none>, Id: _22, Ty: usize, Value: <dead>
Name: <none>, Id: _23, Ty: bool, Value: true
Name: <none>, Id: _24, Ty: std::mem::MaybeUninit<[u8; 2]>, Value: <dead>
Name: integer_and_pointer, Id: _25, Ty: IntegerAndPointer<'_>, Value: [11 22 33 44 __ __ __ __ {ALLOC_PTR}]
Name: integer_and_pointer, Id: _25, Ty: IntegerAndPointer<'_>, Value: IntegerAndPointer { integer: [11 22 33 44], ptr: [{ALLOC_PTR}] }
Name: <none>, Id: _26, Ty: &u8, Value: <dead>
Name: <none>, Id: _27, Ty: &u8, Value: <dead>
Name: <none>, Id: _28, Ty: usize, Value: <dead>
Name: <none>, Id: _29, Ty: bool, Value: true
Name: fixed_addr_ptr, Id: _30, Ty: *const u8, Value: [0x1234[wildcard]]
Name: short_pointer_bytes, Id: _31, Ty: [u8; 1], Value: [34]
Name: short_pointer_bytes, Id: _31, Ty: [u8; 1], Value: [[34]]
Name: bytes, Id: _32, Ty: std::mem::MaybeUninit<[u8; 1]>, Value: <dead>
Name: <none>, Id: _33, Ty: (), Value: <dead>
Name: <none>, Id: _34, Ty: *const u8, Value: <dead>
Expand All @@ -52,5 +52,5 @@ Name: <none>, Id: _47, Ty: &UninitAroundPointer<'_>, Value: <dead>
Name: <none>, Id: _48, Ty: &IntegerAndPointer<'_>, Value: <dead>
Name: <none>, Id: _49, Ty: &*const u8, Value: <dead>
Name: <none>, Id: _50, Ty: &[u8; 1], Value: <dead>
(priroda) Id: _2, Ty: PointerAtOffset0<'_>, Value: [{ALLOC_PTR}]
(priroda) Id: _2, Ty: PointerAtOffset0<'_>, Value: PointerAtOffset0 { ptr: [{ALLOC_PTR}] }
(priroda) quitting
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
(priroda) Hit breakpoint
{MANIFEST_DIR}/tests/ui/locals_projected_mplace_size.rs:36
(priroda) Name: <none>, Id: _0, Ty: (), Value: <uninit>
Name: <none>, Id: _1, Ty: Envelope, Value: [aa __ __ __ 22 11 33 __ 77 66 55 44 88 __ __ __ 00 ff ee dd cc bb aa 99 34 12 __ __ __ __ __ __]
Name: <none>, Id: _1, Ty: Envelope, Value: Envelope { prefix: [aa], target: Payload { a: [22 11], b: [33], c: [77 66 55 44], d: [88] }, trailer: [00 ff ee dd cc bb aa 99], checksum: [34 12] }
Name: prefix, Id: _1.0, Ty: u8, Value: [aa]
Name: target, Id: _1.1, Ty: Payload, Value: [22 11 33 __ 77 66 55 44 88 __ __ __]
Name: target, Id: _1.1, Ty: Payload, Value: Payload { a: [22 11], b: [33], c: [77 66 55 44], d: [88] }
Name: trailer, Id: _1.2, Ty: u64, Value: [00 ff ee dd cc bb aa 99]
Name: checksum, Id: _1.3, Ty: u16, Value: [34 12]
(priroda) quitting
Loading
Loading