From 7965d3d2c001aec9bf0892075dac636222697d52 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Tue, 1 Oct 2024 22:54:54 -0600 Subject: [PATCH 1/9] panic runtime and C-unwind documentation --- src/SUMMARY.md | 2 + src/behavior-considered-undefined.md | 16 ++++- src/crates-and-source-files.md | 14 ++++ src/destructors.md | 25 ++++++- src/expressions/array-expr.md | 3 +- src/items/external-blocks.md | 34 ++++++--- src/items/functions.md | 55 +++++++++++++-- src/linkage.md | 39 +++++++++++ src/panic.md | 101 +++++++++++++++++++++++++++ src/runtime.md | 7 +- 10 files changed, 278 insertions(+), 18 deletions(-) create mode 100644 src/panic.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 91f343b8d..60787c339 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -114,6 +114,8 @@ - [Memory allocation and lifetime](memory-allocation-and-lifetime.md) - [Variables](variables.md) +- [Panic](panic.md) + - [Linkage](linkage.md) - [Inline assembly](inline-assembly.md) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 424ef228f..d57c596ae 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -78,7 +78,9 @@ r[undefined.target-feature] does not support (see [`target_feature`]), *except* if the platform explicitly documents this to be safe. r[undefined.call] -* Calling a function with the wrong call ABI or unwinding from a function with the wrong unwind ABI. +* Calling a function with the wrong [call ABI][abi], or unwinding past a stack + frame that does not allow unwinding (e.g. by calling a `"C-unwind"` function + imported or transmuted as a `"C"` function or function pointer). r[undefined.invalid] * Producing an [invalid value][invalid-values]. "Producing" a @@ -97,6 +99,16 @@ r[undefined.const-transmute-ptr2int] 'Reinterpreting' refers to loading the pointer value at integer type without a cast, e.g. by doing raw pointer casts or using a union. +r[undefined.runtime] +* Violating assumptions of the Rust runtime. This is only possible using + mechanisms outside Rust. Most assumptions of the Rust runtime are currently + not explicitly documented. + * For assumptions specifically related to unwinding, see the [panic + documentation][unwinding-ffi]. + * The runtime assumes that a Rust stack frame is not deallocated without + executing destructors for local variables owned by the stack frame. This assumption + can be violated by C functions like `longjmp`. + > **Note**: Undefined behavior affects the entire program. For example, calling > a function in C that exhibits undefined behavior of C means your entire > program contains undefined behaviour that can also affect the Rust code. And @@ -246,6 +258,7 @@ reading uninitialized memory is permitted are inside `union`s and in "padding" [`const`]: items/constant-items.md [noalias]: http://llvm.org/docs/LangRef.html#noalias [pointer aliasing rules]: http://llvm.org/docs/LangRef.html#pointer-aliasing-rules +[abi]: abi.md [undef]: http://llvm.org/docs/LangRef.html#undefined-values [`target_feature`]: attributes/codegen.md#the-target_feature-attribute [`UnsafeCell`]: std::cell::UnsafeCell @@ -259,5 +272,6 @@ reading uninitialized memory is permitted are inside `union`s and in "padding" [project-field]: expressions/field-expr.md [project-tuple]: expressions/tuple-expr.md#tuple-indexing-expressions [project-slice]: expressions/array-expr.md#array-and-slice-indexing-expressions +[unwinding-ffi]: panic.md#unwinding-across-ffi-boundaries [const-promoted]: destructors.md#constant-promotion [lifetime-extended]: destructors.md#temporary-lifetime-extension diff --git a/src/crates-and-source-files.md b/src/crates-and-source-files.md index 734ee3e98..2b799e963 100644 --- a/src/crates-and-source-files.md +++ b/src/crates-and-source-files.md @@ -124,6 +124,19 @@ use foo::bar as main; +### Uncaught foreign unwinding + +r[crate.uncaught-foreign-unwinding] + +When a "foreign" unwind (e.g. an exception thrown from C++ code, or a `panic!` +in Rust code compiled or linked with a different runtime) is not caught before +reaching the `main` function, the process will be safely terminated. This may +take the form of an abort, in which case it is not guaranteed that any `Drop` +calls will be executed, and the error output may be less informative than if the +runtime had been terminated by a "native" Rust `panic`. + +For more information, see the [panic documentation][panic-docs]. + ### The `no_main` attribute r[crate.no_main] @@ -169,6 +182,7 @@ or `_` (U+005F) characters. [function]: items/functions.md [module]: items/modules.md [module path]: paths.md +[panic-docs]: panic.md#unwinding-across-ffi-boundaries [shebang]: input-format.md#shebang-removal [trait or lifetime bounds]: trait-bounds.md [where clauses]: items/generics.md#where-clauses diff --git a/src/destructors.md b/src/destructors.md index 9870b0148..db8ef26e1 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -268,7 +268,8 @@ Temporaries are also created to hold the result of operands to an expression while the other operands are evaluated. The temporaries are associated to the scope of the expression with that operand. Since the temporaries are moved from once the expression is evaluated, dropping them has no effect unless one of the -operands to an expression breaks out of the expression, returns, or panics. +operands to an expression breaks out of the expression, returns, or +[panics][panic]. ```rust # struct PrintOnDrop(&'static str); @@ -419,6 +420,8 @@ let x = (&temp()).use_temp(); // ERROR ## Not running destructors +### `forget` + r[destructors.forget] [`std::mem::forget`] can be used to prevent the destructor of a variable from being run, @@ -428,6 +431,22 @@ variable or field from being dropped automatically. > Note: Preventing a destructor from being run via [`std::mem::forget`] or other means is safe even if it has a type that isn't `'static`. > Besides the places where destructors are guaranteed to run as defined by this document, types may *not* safely rely on a destructor being run for soundness. +### Process termination without unwinding + +r[destructors.process-termination] + +There are some ways to terminate the process without [unwinding], in which case +destructors will not be run. + +The standard library provides [`std::process::exit`] and +[`std::process::abort`] to do this explicitly. Additionally, if the +[panic-mode] is set to `abort`, panicking will always terminate the process +without destructors being run. + +There is one additional case to be aware of: when a panic reaches a +[non-unwinding ABI boundary], either no destructors will run, or all +destructors up until the ABI boundary will run. + [Assignment]: expressions/operator-expr.md#assignment-expressions [binding modes]: patterns.md#binding-modes [closure]: types/closure.md @@ -437,11 +456,15 @@ variable or field from being dropped automatically. [initialized]: glossary.md#initialized [interior mutability]: interior-mutability.md [lazy boolean expression]: expressions/operator-expr.md#lazy-boolean-operators +[non-unwinding ABI boundary]: items/functions.md#unwinding +[panic]: panic.md +[panic-mode]: panic.md#panic-runtimes [place context]: expressions.md#place-expressions-and-value-expressions [promoted]: destructors.md#constant-promotion [scrutinee]: glossary.md#scrutinee [statement]: statements.md [temporary]: expressions.md#temporaries +[unwinding]: panic.md#unwinding [variable]: variables.md [array]: types/array.md diff --git a/src/expressions/array-expr.md b/src/expressions/array-expr.md index 52d31940d..66f69c93f 100644 --- a/src/expressions/array-expr.md +++ b/src/expressions/array-expr.md @@ -53,7 +53,7 @@ Just as with methods, Rust will also insert dereference operations on `a` repeat Indices are zero-based for arrays and slices. Array access is a [constant expression], so bounds can be checked at compile-time with a constant index value. -Otherwise a check will be performed at run-time that will put the thread in a _panicked state_ if it fails. +Otherwise a check will be performed at run-time that will put the thread in a [_panicked state_][panic] if it fails. ```rust,should_panic // lint is deny by default. @@ -84,5 +84,6 @@ The array index expression can be implemented for types other than arrays and sl [constant item]: ../items/constant-items.md [literal]: ../tokens.md#literals [memory location]: ../expressions.md#place-expressions-and-value-expressions +[panic]: ../panic.md [path]: path-expr.md [slice]: ../types/slice.md diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index 5839f242b..dc6d7e490 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -102,7 +102,7 @@ unsafe extern "stdcall" { } ``` r[items.extern.abi.standard] -There are three ABI strings which are cross-platform, and which all compilers +There are five ABI strings which are cross-platform, and which all compilers are guaranteed to support: r[items.extern.abi.rust] @@ -118,6 +118,11 @@ r[items.extern.abi.system] which case it's `"stdcall"`, or what you should use to link to the Windows API itself +r[items.extern.abi.unwind] +* `extern "C-unwind"` and `extern "system-unwind"` -- identical to `"C"` and + `"system"`, respectively, but with [different behavior][unwind-behavior] when + the callee unwinds (by panicking or throwing a C++ style exception). + r[items.extern.abi.platform] There are also some platform-specific ABI strings: @@ -151,6 +156,18 @@ r[items.extern.abi.thiscall] r[items.extern.abi.efiapi] * `unsafe extern "efiapi"` -- The ABI used for [UEFI] functions. +Like `"C"` and `"system"`, most platform-specific ABI strings also have a +[corresponding `-unwind` variant][unwind-behavior]; specifically, these are: + +* `"cdecl-unwind"` +* `"stdcall-unwind"` +* `"fastcall-unwind"` +* `"vectorcall-unwind"` +* `"thiscall-unwind"` +* `"aapcs-unwind"` +* `"win64-unwind"` +* `"sysv64-unwind"` + ## Variadic functions r[items.extern.variadic] @@ -439,10 +456,9 @@ Attributes on extern function parameters follow the same rules and restrictions as [regular function parameters]. [IDENTIFIER]: ../identifiers.md +[PE Format]: https://learn.microsoft.com/windows/win32/debug/pe-format#import-name-type [UEFI]: https://uefi.org/specifications [WebAssembly module]: https://webassembly.github.io/spec/core/syntax/modules.html -[functions]: functions.md -[statics]: static-items.md [_Abi_]: functions.md [_Function_]: functions.md [_InnerAttribute_]: ../attributes.md @@ -452,11 +468,13 @@ restrictions as [regular function parameters]. [_OuterAttribute_]: ../attributes.md [_StaticItem_]: static-items.md [_Visibility_]: ../visibility-and-privacy.md -[attributes]: ../attributes.md -[regular function parameters]: functions.md#attributes-on-function-parameters [`bundle` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-bundle -[`whole-archive` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-whole-archive -[`verbatim` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-verbatim [`dylib` versus `raw-dylib`]: #dylib-versus-raw-dylib -[PE Format]: https://learn.microsoft.com/windows/win32/debug/pe-format#import-name-type +[`verbatim` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-verbatim +[`whole-archive` documentation for rustc]: ../../rustc/command-line-arguments.html#linking-modifiers-whole-archive +[attributes]: ../attributes.md +[functions]: functions.md +[regular function parameters]: functions.md#attributes-on-function-parameters +[statics]: static-items.md +[unwind-behavior]: functions.md#unwinding [value namespace]: ../names/namespaces.md diff --git a/src/items/functions.md b/src/items/functions.md index 8300ad19d..4acea348c 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -254,13 +254,58 @@ extern "C" fn new_i32() -> i32 { 0 } let fptr: extern "C" fn() -> i32 = new_i32; ``` +### Unwinding + r[items.fn.extern.unwind] -Functions with an ABI that differs from `"Rust"` do not support unwinding in the -exact same way that Rust does. Therefore, unwinding past the end of functions -with such ABIs causes the process to abort. -> **Note**: The LLVM backend of the `rustc` implementation -aborts the process by executing an illegal instruction. +r[items.fn.extern.unwind.into] +Most ABI strings come in two variants, one with an `-unwind` suffix and one without. +The `Rust` ABI always permits unwinding, so there is no `Rust-unwind` ABI. The +choice of ABI, together with the runtime [panic mode][panic-modes], determines +the behavior when unwinding out of a function. + +The table below indicates the behavior of an unwinding operation reaching each +type of ABI boundary (function declaration or definition using the +corresponding ABI string). Note that the Rust runtime is not affected by, and +cannot have an effect on, any unwinding that occurs entirely within another +language's runtime, that is, unwinds that are thrown and caught without +reaching a Rust ABI boundary. + +The `panic`-unwind column refers to [panicking] via the `panic!` macro and +similar standard library mechanisms, as well as to any other Rust operations +that cause a panic, such as out-of-bounds array indexing or integer overflow. + +The "unwinding" ABI category refers to `"Rust"` (the implicit ABI of Rust +functions not marked `extern`), `"C-unwind"`, and any other ABI with `-unwind` +in its name. The "non-unwinding" ABI category refers to all other ABI strings, +including `"C"` and `"stdcall"`. + +Native unwinding is defined per-target. On targets that support throwing and +catching C++ exceptions, it refers to the mechanism used to implement this +feature. Some platforms implement a form of unwinding referred to as ["forced +unwinding"][forced-unwinding]; `longjmp` on Windows and `pthread_exit` in +`glibc` are implemented this way. Forced unwinding is explicitly excluded +from the "Native unwind" column in the table. + +| panic runtime | ABI | `panic`-unwind | Native unwind (unforced) | +| -------------- | ------------ | ------------------------------------- | ----------------------- | +| `panic=unwind` | unwinding | unwind | unwind | +| `panic=unwind` | non-unwinding | abort (see notes below) | [undefined behavior] | +| `panic=abort` | unwinding | `panic` aborts without unwinding | abort | +| `panic=abort` | non-unwinding | `panic` aborts without unwinding | [undefined behavior] | + +> **Note**: With `panic=unwind`, when a `panic` is turned into an abort by a +> non-unwinding ABI boundary, either no destructors (`Drop` calls) will run, or +> all destructors up until the ABI boundary will run. + +For other considerations and limitations regarding unwinding across FFI +boundaries, see the [relevant section in the Panic documentation][panic-ffi]. + +[forced-unwinding]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html#forced-unwinding +[panic-modes]: ../panic.md#panic-runtimes +[panic-ffi]: ../panic.md#unwinding-across-ffi-boundaries +[panicking]: ../panic.md +[undefined behavior]: ../behavior-considered-undefined.md ## Const functions diff --git a/src/linkage.md b/src/linkage.md index ff41a140f..47c63a097 100644 --- a/src/linkage.md +++ b/src/linkage.md @@ -254,6 +254,9 @@ RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-pc-windows ## Mixed Rust and foreign codebases +r[link.foreign-code] + +r[link.foreign-code.foreign-linkers] If you are mixing Rust with foreign code (e.g. C, C++) and wish to make a single binary containing both types of code, you have two approaches for the final binary link: @@ -269,6 +272,42 @@ binary link: Passing `rlib`s directly into your foreign linker is currently unsupported. +### Prohibited linkage and foreign unwinding + +r[link.foreign-code.prohibited] + +Undfined behavior may be caused by foreign code unwinding into a Rust crate +with these characteristics: + +* The foreign unwind enters Rust via a function or function pointer declared + with an ABI that permits unwinding, that is, `"Rust"` (the default ABI) or + any `-unwind` ABI +* The Rust crate containing the `-unwind` ABI declaration was compiled with + `panic=unwind` +* The final binary is linked with [the `panic=abort` runtime][panic-runtime] + +> **Note**: To protect against this undefined behavior, `rustc` does not permit +> linking the `panic=abort` runtime against any crate that was compiled with +> `panic=unwind` if that crate also contains a call to a foreign function or +> function pointer declared with an `-unwind` ABI. Note that this prohibition +> applies even when linking a static or dynamic library that only includes Rust +> code, since the resulting library may be subsequently linked against another +> library that may unwind. However, use of the `Rust` (default) ABI does not +> cause a link-error, since that ABI is not expected to be used as an +> entrypoint into a static or shared library. + +r[link.foreign-code.prohibited.lint.ffi_unwind_calls] +To guarantee that a library will be sound (and linkable with `rustc`) +regardless of the panic mode used at link-time, the [`ffi_unwind_calls` lint] +may be used. The lint flags any calls to `-unwind` foreign functions or +function pointers. + +> **Note**: the restriction can only be violated when mixing code with different +> `-C panic` flags. This is not possible in normal use of cargo, so most users +> need not be concerned about this. + [`cfg` attribute `target_feature` option]: conditional-compilation.md#target_feature +[`ffi_unwind_calls` lint]: ../rustc/lints/listing/allowed-by-default.html#ffi-unwind-calls [configuration option]: conditional-compilation.md +[panic-runtime]: panic.md#panic-runtimes [procedural macros]: procedural-macros.md diff --git a/src/panic.md b/src/panic.md new file mode 100644 index 000000000..b0462dbd1 --- /dev/null +++ b/src/panic.md @@ -0,0 +1,101 @@ +# Panic + +Rust provides a mechanism to prevent a function from returning normally, and +instead "panic," which is a response to an error condition that is typically +not expected to be recoverable within the context in which the error is +encountered. + +Some language constructs, such as out-of-bounds [array indexing], panic +automatically. There are also language features that provide a level of control +over panic behavior: +* A [_panic runtime_](#panic-runtimes) defined how a panic is handled during + runtime. +* [FFI ABIs](items/functions.md#unwinding) may alter how panics behave. + +> **Note**: The standard library provides the capability to explicitly panic +> via the [`panic!` macro][macro-panic]. + +## Unwinding + +Panicking may either be recoverable or non-recoverable, though it can be +configured (via `panic=abort`) to always be non-recoverable. (The converse is +not true: `panic=unwind` does not guarantee that all panics are recoverable, +only that panicking via the `panic!` macro and similar standard library +mechanisms is recoverable.) When panic recovery occurs, the runtime "unwinds" +Rust frames, just as C++'s `throw` unwinds C++ frames, until the panic reaches +the point of recovery (for instance at a thread boundary). This means that as +the panic traverses Rust frames, live objects in those frames that [implement +`Drop`][destructors] will have their `drop` methods called. Thus, when normal +execution resumes, no-longer-accessible objects will have been "cleaned up" +just as if they had gone out of scope normally. + +> **Note**: As long as this guarantee of resource-cleanup is preserved, +> "unwinding" may be implemented without actually using the mechanism used by +> C++ for the target platform. + +> **Note**: The standard library provides two mechanisms for recovering from a panic, +> [`catch_unwind`][fn-catch-unwind] (which enables recovery within the +> panicking thread) and [`thread::spawn`][thread-spawn] (which automatically +> sets up panic recovery for the spawned thread so that other threads may +> continue running). + +### Unwinding across FFI boundaries + +It is possible to unwind across FFI boundaries using an [appropriate ABI +declaration][unwind-abi]. While useful in certain cases, this creates unique +opportunities for undefined behavior, especially when multiple language runtimes +are involved. + +Unwinding with the wrong ABI is undefined behavior: + +* Causing an unwind into Rust code from a foreign function that was called via a + function declaration or pointer declared with a non-unwinding ABI, such as `"C"`, + `"system"`, etc. (For example, this case occurs when such a function written in + C++ throws an exception that is uncaught and propagates to Rust.) +* Calling a Rust `extern` function that unwinds (with `extern "C-unwind"` or + another ABI that permits unwinding) from a runtime that does not support. + unwinding, such as code compiled with GCC or Clang using `-fno-exceptions` + +Catching a foreign unwinding operation (such as a C++ exception) using +`catch_unwind`, `JoinHandle::join`, or by letting it propagate all the way to a +Rust `main()` function will have one of two behaviors, and it is unspecified +which will occur: +* The process aborts. +* The function returns a `Result::Err` containing an opaque type. + +Note that Rust code compiled or linked with a different runtime counts as a +"foreign exception" for the purpose of this guarantee. Thus, a library that +uses `panic!` and is linked against one version of the Rust standard library, +invoked from an application that uses a different version of the standard +library, may cause the entire application to crash even if the library is only +used within a child thread. + +There are currently no guarantees about the behavior that occurs when a foreign +runtime attempts to dispose of, or rethrow, a Rust `panic` payload. In other +words, an unwind originated from a Rust runtime must either lead to termination +of the process or be caught by the same runtime. + +## Panic runtimes + +The actual behavior and implementation of `panic!` is controlled by the _panic +runtime_. + +> **Note**: The Rust standard library provides two panic runtimes: +> `panic_unwind` (which unwinds the stack and is potentially recoverable) and +> `panic_abort` (which aborts the process and is non-recoverable). The default +> runtime depends on the target platform, but is generally `panic_unwind` on +> platforms with native support for C++ exceptions. + +When compiling code that is guaranteed to be linked to a non-recoverable panic +runtime, the optimizer may assume that unwinding across Rust frames is +impossible, which can result in both code-size and runtime speed improvements. + +See also the [`panic_handler` attribute](runtime.md#the-panic_handler-attribute) which can be used to change the behavior of panics. + +[array indexing]: expressions/array-expr.md#array-and-slice-indexing-expressions +[destructors]: destructors.md +[fn-catch-unwind]: ../std/panic/fn.catch_unwind.html +[macro-panic]: ../std/macro.panic.html +[runtime]: runtime.md +[thread-spawn]: ../std/thread/fn.spawn.html +[unwind-abi]: items/functions.md#unwinding diff --git a/src/runtime.md b/src/runtime.md index a673834f8..6b3f4a23a 100644 --- a/src/runtime.md +++ b/src/runtime.md @@ -47,8 +47,10 @@ fn panic(info: &PanicInfo) -> ! { The standard library provides an implementation of `panic_handler` that defaults to unwinding the stack but that can be [changed to abort the -process][abort]. The standard library's panic behavior can be modified at -runtime with the [set_hook] function. +process][abort]. See [panic runtimes] for more details. + +The standard library's panic behavior can be modified at runtime with the +[`set_hook` function][set_hook]. ## The `global_allocator` attribute @@ -80,6 +82,7 @@ display a console window on startup. It will run detached from any existing cons [abort]: ../book/ch09-01-unrecoverable-errors-with-panic.html [attribute]: attributes.md [crate types]: linkage.md +[panic runtimes]: panic.md#panic-runtimes [set_hook]: std::panic::set_hook [static item]: items/static-items.md [subsystem]: https://msdn.microsoft.com/en-us/library/fcc1zstk.aspx From d1339c4d77a06f35dd1838a06b5b8490b6faf38e Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Sat, 23 Nov 2024 21:30:09 -0700 Subject: [PATCH 2/9] Rust version number for destructors guarantee Co-authored-by: Daira-Emma Hopwood --- src/items/functions.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/items/functions.md b/src/items/functions.md index 4acea348c..17a1421c3 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -294,9 +294,10 @@ from the "Native unwind" column in the table. | `panic=abort` | unwinding | `panic` aborts without unwinding | abort | | `panic=abort` | non-unwinding | `panic` aborts without unwinding | [undefined behavior] | -> **Note**: With `panic=unwind`, when a `panic` is turned into an abort by a -> non-unwinding ABI boundary, either no destructors (`Drop` calls) will run, or -> all destructors up until the ABI boundary will run. +> **Note**: The following guarantee applies from Rust 1.82 onward: with +> `panic=unwind`, when a `panic` is turned into an abort by a non-unwinding +> ABI boundary, either no destructors (`Drop` calls) will run, or all destructors +> up until the ABI boundary will run. For other considerations and limitations regarding unwinding across FFI boundaries, see the [relevant section in the Panic documentation][panic-ffi]. From 6ce66b47662d9ddbeeb0ac96b9b0b5e941d4bbd5 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Mon, 25 Nov 2024 14:59:17 -0500 Subject: [PATCH 3/9] Fix identifeirs in functions.md and move the guarantee about destructor execution out of a note --- src/items/functions.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/items/functions.md b/src/items/functions.md index 17a1421c3..7479722ce 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -258,12 +258,13 @@ let fptr: extern "C" fn() -> i32 = new_i32; r[items.fn.extern.unwind] -r[items.fn.extern.unwind.into] +r[items.fn.extern.unwind.info] Most ABI strings come in two variants, one with an `-unwind` suffix and one without. The `Rust` ABI always permits unwinding, so there is no `Rust-unwind` ABI. The choice of ABI, together with the runtime [panic mode][panic-modes], determines the behavior when unwinding out of a function. +r[items.fn.extern.unwind.behavior] The table below indicates the behavior of an unwinding operation reaching each type of ABI boundary (function declaration or definition using the corresponding ABI string). Note that the Rust runtime is not affected by, and @@ -294,10 +295,12 @@ from the "Native unwind" column in the table. | `panic=abort` | unwinding | `panic` aborts without unwinding | abort | | `panic=abort` | non-unwinding | `panic` aborts without unwinding | [undefined behavior] | -> **Note**: The following guarantee applies from Rust 1.82 onward: with -> `panic=unwind`, when a `panic` is turned into an abort by a non-unwinding -> ABI boundary, either no destructors (`Drop` calls) will run, or all destructors -> up until the ABI boundary will run. +r[items.fn.extern.abort] +With `panic=unwind`, when a `panic` is turned into an abort by a non-unwinding ABI boundary, either no destructors (`Drop` calls) will run, or all destructors +up until the ABI boundary will run. + +> [!NOTE] +> Prior to Rust 1.82, it is possible for only some of the destructors to executive, and not others. For other considerations and limitations regarding unwinding across FFI boundaries, see the [relevant section in the Panic documentation][panic-ffi]. From 276ff8e1593c7af30270788a0b89d6d650b90fc5 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Mon, 25 Nov 2024 15:07:09 -0500 Subject: [PATCH 4/9] Add identifier syntax to panic.md --- src/panic.md | 58 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/panic.md b/src/panic.md index b0462dbd1..8066f85aa 100644 --- a/src/panic.md +++ b/src/panic.md @@ -1,39 +1,54 @@ # Panic +r[panic] + +r[panic.intro] Rust provides a mechanism to prevent a function from returning normally, and instead "panic," which is a response to an error condition that is typically not expected to be recoverable within the context in which the error is encountered. +r[panic.lang-ops] Some language constructs, such as out-of-bounds [array indexing], panic -automatically. There are also language features that provide a level of control +automatically. + +r[panic.control] +There are also language features that provide a level of control over panic behavior: * A [_panic runtime_](#panic-runtimes) defined how a panic is handled during runtime. * [FFI ABIs](items/functions.md#unwinding) may alter how panics behave. -> **Note**: The standard library provides the capability to explicitly panic +> [!NOTE] +> The standard library provides the capability to explicitly panic > via the [`panic!` macro][macro-panic]. ## Unwinding +r[panic.unwind] + +r[panic.unwind.intro] Panicking may either be recoverable or non-recoverable, though it can be configured (via `panic=abort`) to always be non-recoverable. (The converse is not true: `panic=unwind` does not guarantee that all panics are recoverable, only that panicking via the `panic!` macro and similar standard library -mechanisms is recoverable.) When panic recovery occurs, the runtime "unwinds" -Rust frames, just as C++'s `throw` unwinds C++ frames, until the panic reaches +mechanisms is recoverable.) + +r[panic.unwind.destruction] +When panic recovery occurs, the runtime "unwinds" Rust frames, just as C++'s `throw` unwinds C++ frames, until the panic reaches the point of recovery (for instance at a thread boundary). This means that as the panic traverses Rust frames, live objects in those frames that [implement `Drop`][destructors] will have their `drop` methods called. Thus, when normal execution resumes, no-longer-accessible objects will have been "cleaned up" just as if they had gone out of scope normally. -> **Note**: As long as this guarantee of resource-cleanup is preserved, +> [!NOTE] +> As long as this guarantee of resource-cleanup is preserved, > "unwinding" may be implemented without actually using the mechanism used by > C++ for the target platform. -> **Note**: The standard library provides two mechanisms for recovering from a panic, +> [!NOTE] +> The standard library provides two mechanisms for recovering from a panic, > [`catch_unwind`][fn-catch-unwind] (which enables recovery within the > panicking thread) and [`thread::spawn`][thread-spawn] (which automatically > sets up panic recovery for the spawned thread so that other threads may @@ -41,11 +56,15 @@ just as if they had gone out of scope normally. ### Unwinding across FFI boundaries +r[panic.unwind.ffi] + +r[panic.unwind.ffi.intro] It is possible to unwind across FFI boundaries using an [appropriate ABI declaration][unwind-abi]. While useful in certain cases, this creates unique opportunities for undefined behavior, especially when multiple language runtimes are involved. +r[panic.unwind.ffi.undefined] Unwinding with the wrong ABI is undefined behavior: * Causing an unwind into Rust code from a foreign function that was called via a @@ -56,6 +75,7 @@ Unwinding with the wrong ABI is undefined behavior: another ABI that permits unwinding) from a runtime that does not support. unwinding, such as code compiled with GCC or Clang using `-fno-exceptions` +r[panic.unwind.ffi.catch-foreign] Catching a foreign unwinding operation (such as a C++ exception) using `catch_unwind`, `JoinHandle::join`, or by letting it propagate all the way to a Rust `main()` function will have one of two behaviors, and it is unspecified @@ -63,13 +83,15 @@ which will occur: * The process aborts. * The function returns a `Result::Err` containing an opaque type. -Note that Rust code compiled or linked with a different runtime counts as a -"foreign exception" for the purpose of this guarantee. Thus, a library that -uses `panic!` and is linked against one version of the Rust standard library, -invoked from an application that uses a different version of the standard -library, may cause the entire application to crash even if the library is only -used within a child thread. +> [!NOTE] +> Rust code compiled or linked with a different runtime counts as a +> "foreign exception" for the purpose of this guarantee. Thus, a library that +> uses `panic!` and is linked against one version of the Rust standard library, +> invoked from an application that uses a different version of the standard +> library, may cause the entire application to crash even if the library is only +> used within a child thread. +r[panic.unwind.ffi.dipose-panic] There are currently no guarantees about the behavior that occurs when a foreign runtime attempts to dispose of, or rethrow, a Rust `panic` payload. In other words, an unwind originated from a Rust runtime must either lead to termination @@ -77,18 +99,22 @@ of the process or be caught by the same runtime. ## Panic runtimes +r[panic.runtime] + The actual behavior and implementation of `panic!` is controlled by the _panic runtime_. -> **Note**: The Rust standard library provides two panic runtimes: +> [!NOTE] +> The Rust standard library provides two panic runtimes: > `panic_unwind` (which unwinds the stack and is potentially recoverable) and > `panic_abort` (which aborts the process and is non-recoverable). The default > runtime depends on the target platform, but is generally `panic_unwind` on > platforms with native support for C++ exceptions. -When compiling code that is guaranteed to be linked to a non-recoverable panic -runtime, the optimizer may assume that unwinding across Rust frames is -impossible, which can result in both code-size and runtime speed improvements. +> [!NOTE] +> When compiling code that is guaranteed to be linked to a non-recoverable panic +> runtime, the optimizer may assume that unwinding across Rust frames is +> impossible, which can result in both code-size and runtime speed improvements. See also the [`panic_handler` attribute](runtime.md#the-panic_handler-attribute) which can be used to change the behavior of panics. From a540310c3c8b41218cff9e34cbf54fadd959dfa1 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Mon, 25 Nov 2024 15:09:22 -0500 Subject: [PATCH 5/9] Move note in linkage.md --- src/linkage.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/linkage.md b/src/linkage.md index 47c63a097..e1251ccc0 100644 --- a/src/linkage.md +++ b/src/linkage.md @@ -296,15 +296,16 @@ with these characteristics: > cause a link-error, since that ABI is not expected to be used as an > entrypoint into a static or shared library. +> **Note**: the restriction can only be violated when mixing code with different +> `-C panic` flags. This is not possible in normal use of cargo, so most users +> need not be concerned about this. + r[link.foreign-code.prohibited.lint.ffi_unwind_calls] To guarantee that a library will be sound (and linkable with `rustc`) regardless of the panic mode used at link-time, the [`ffi_unwind_calls` lint] may be used. The lint flags any calls to `-unwind` foreign functions or function pointers. -> **Note**: the restriction can only be violated when mixing code with different -> `-C panic` flags. This is not possible in normal use of cargo, so most users -> need not be concerned about this. [`cfg` attribute `target_feature` option]: conditional-compilation.md#target_feature [`ffi_unwind_calls` lint]: ../rustc/lints/listing/allowed-by-default.html#ffi-unwind-calls From 28b3ffd5be626f20346be8ae56f2f4e8b4a6eb3e Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Mon, 25 Nov 2024 15:24:50 -0500 Subject: [PATCH 6/9] Fix whitespace issues in previous commit --- src/panic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panic.md b/src/panic.md index 8066f85aa..203a31cfa 100644 --- a/src/panic.md +++ b/src/panic.md @@ -32,7 +32,7 @@ Panicking may either be recoverable or non-recoverable, though it can be configured (via `panic=abort`) to always be non-recoverable. (The converse is not true: `panic=unwind` does not guarantee that all panics are recoverable, only that panicking via the `panic!` macro and similar standard library -mechanisms is recoverable.) +mechanisms is recoverable.) r[panic.unwind.destruction] When panic recovery occurs, the runtime "unwinds" Rust frames, just as C++'s `throw` unwinds C++ frames, until the panic reaches From 621dac905b0e0dde70d3de9c8e13c98b09f36590 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Mon, 25 Nov 2024 15:25:48 -0500 Subject: [PATCH 7/9] Fix omitted whitespace issue (thanks VSC) --- src/linkage.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/linkage.md b/src/linkage.md index e1251ccc0..76bc5a673 100644 --- a/src/linkage.md +++ b/src/linkage.md @@ -306,7 +306,6 @@ regardless of the panic mode used at link-time, the [`ffi_unwind_calls` lint] may be used. The lint flags any calls to `-unwind` foreign functions or function pointers. - [`cfg` attribute `target_feature` option]: conditional-compilation.md#target_feature [`ffi_unwind_calls` lint]: ../rustc/lints/listing/allowed-by-default.html#ffi-unwind-calls [configuration option]: conditional-compilation.md From db97d545e06f6274d5e91c0e8d4afb71cda2b7e6 Mon Sep 17 00:00:00 2001 From: Connor Horman Date: Tue, 26 Nov 2024 18:15:04 -0500 Subject: [PATCH 8/9] Update src/items/functions.md Co-authored-by: Ralf Jung --- src/items/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/functions.md b/src/items/functions.md index 7479722ce..0debf46b7 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -300,7 +300,7 @@ With `panic=unwind`, when a `panic` is turned into an abort by a non-unwinding A up until the ABI boundary will run. > [!NOTE] -> Prior to Rust 1.82, it is possible for only some of the destructors to executive, and not others. +> Prior to Rust 1.82, it is possible for only some of the destructors to execute, and not others. For other considerations and limitations regarding unwinding across FFI boundaries, see the [relevant section in the Panic documentation][panic-ffi]. From 117ca17f31c90b9f9687b6282bc37d33c14a6a90 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 29 Nov 2024 17:31:30 +0100 Subject: [PATCH 9/9] fix wording regarding UB due to unwinding linkage --- src/linkage.md | 62 +++++++++++++++++++++++++++----------------------- src/panic.md | 2 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/linkage.md b/src/linkage.md index 76bc5a673..1ee1a0e90 100644 --- a/src/linkage.md +++ b/src/linkage.md @@ -272,35 +272,39 @@ binary link: Passing `rlib`s directly into your foreign linker is currently unsupported. -### Prohibited linkage and foreign unwinding - -r[link.foreign-code.prohibited] - -Undfined behavior may be caused by foreign code unwinding into a Rust crate -with these characteristics: - -* The foreign unwind enters Rust via a function or function pointer declared - with an ABI that permits unwinding, that is, `"Rust"` (the default ABI) or - any `-unwind` ABI -* The Rust crate containing the `-unwind` ABI declaration was compiled with - `panic=unwind` -* The final binary is linked with [the `panic=abort` runtime][panic-runtime] - -> **Note**: To protect against this undefined behavior, `rustc` does not permit -> linking the `panic=abort` runtime against any crate that was compiled with -> `panic=unwind` if that crate also contains a call to a foreign function or -> function pointer declared with an `-unwind` ABI. Note that this prohibition -> applies even when linking a static or dynamic library that only includes Rust -> code, since the resulting library may be subsequently linked against another -> library that may unwind. However, use of the `Rust` (default) ABI does not -> cause a link-error, since that ABI is not expected to be used as an -> entrypoint into a static or shared library. - -> **Note**: the restriction can only be violated when mixing code with different -> `-C panic` flags. This is not possible in normal use of cargo, so most users -> need not be concerned about this. - -r[link.foreign-code.prohibited.lint.ffi_unwind_calls] +> [!NOTE] +> Rust code compiled or linked with a different instance of the Rust runtime counts as a +> "foreign code" for the purpose of this section. + +### Prohibited linkage and unwinding + +r[link.unwinding] +If you are *not* using `rustc` to link Rust files, you must take care to ensure that unwinding is +handled consistently across the entire binary. This includes using `dlopen` or similar facilities +where linking is done by the system runtime without `rustc` being involved. + +r[link.unwinding.potential] +A Rust binary or `staticlib` is called *potentially unwinding* if any of the following conditions +is met: +- The binary or `staticlib` is linked with [the `panic=unwind` runtime][panic-runtime]. +- The binary or `staticlib` contains a crate built with `-Cpanic=unwind` that makes a call + to a function using a `-unwind` ABI. +- The binary or `staticlib` makes a `"Rust"` ABI call to code running in a separately built + `staticlib` (i.e., a separate instance of the Rust runtime), and that other `staticlib` is + potentially unwinding. + +> [!NOTE] +> This definition captures whether a `"Rust"` ABI call inside a Rust binary or `staticlib` can ever +> unwind. + +r[link.unwinding.prohibited] +If a Rust binary or `staticlib` is potentially unwinding, then all its crates must be built with `-Cpanic=unwind`. + +> [!NOTE] +> This restriction can only be violated when mixing code with different `-C panic` flags +> while also using a non-`rustc` linker. Most users to not have to be concerned about this. + +r[link.unwinding.prohibited.lint.ffi_unwind_calls] To guarantee that a library will be sound (and linkable with `rustc`) regardless of the panic mode used at link-time, the [`ffi_unwind_calls` lint] may be used. The lint flags any calls to `-unwind` foreign functions or diff --git a/src/panic.md b/src/panic.md index 203a31cfa..af47eb9dd 100644 --- a/src/panic.md +++ b/src/panic.md @@ -84,7 +84,7 @@ which will occur: * The function returns a `Result::Err` containing an opaque type. > [!NOTE] -> Rust code compiled or linked with a different runtime counts as a +> Rust code compiled or linked with a different instance of the Rust runtime counts as a > "foreign exception" for the purpose of this guarantee. Thus, a library that > uses `panic!` and is linked against one version of the Rust standard library, > invoked from an application that uses a different version of the standard