From 7f3f888fefc24be370bcb24f71d24d5c585f458fe1f4448b6f18ddc50659f894 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Thu, 9 Jul 2026 20:57:29 -0400 Subject: [PATCH] Started some work on it. The main section is duplicated because I want to use it as a template, to fill in a few other sections. --- double-static-noexcept.md | 571 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 571 insertions(+) create mode 100644 double-static-noexcept.md diff --git a/double-static-noexcept.md b/double-static-noexcept.md new file mode 100644 index 0000000..f15d47d --- /dev/null +++ b/double-static-noexcept.md @@ -0,0 +1,571 @@ +# `double static noexcept` -- a mechanism for static checking of noexcept blocks + +## Table of Contents + +1. Abstract +2. Discussion +3. Proposal +3. Examples + +## Abstract + +In C++, `noexcept` exists to indicate that a function does not throw an exception. However, +what it actually provides is that a function cannot possibly throw an exception. This is +enforced by "violence", if necessary -- the program will terminate rather than allow +an exception to pass a `noexcept` boundary. Unfortunately, this turns `noexcept` into a +ticking timebomb; any `noexcept( true )` function may unexpectedly call `std::terminate` +at any time, thus aborting a program. This may be undesirable in many different contexts, +from Functional Safety to Program Correctness to Contractual Guarantees. This proposal +provides a mechanism which permits various levels of static guarantees. + +## Discussion + +The `noexcept` facility was added to permit a language level (and type-system level) +facility for promising that a function does not throw and for checking it. However, the +enforcement of this promise is a purely runtime concern. This leads to troublesome +timebombs, such as this code. + +~~~ +void silentlyThrowing() noexcept( false ) { throw 0; } + +void +tickingTimeBomb() noexcept( true ) +{ + if( not ( rand() % 32 ) ) + { + silentlyThrowing(); + } +} + +enum Result { Success, Failure }; + +Result +cannotTolerateExceptions() noexcept( true ) +{ + std::string element; + + try + { + element= someVector.at( 42 ); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + someVector.push_back( std::move( element ) ); + tickingTimeBomb(); + + return Success; +} +~~~ + +While some of the code and functions called in `cannotTolerateExceptions` is capable of +throwing, the caller attempts to resolve exceptions and handle them without program +termination. However, the `tickingTimeBomb` function is a hidden hazard. While +it declares itself to not throw exceptions (which is true), the exceptions thrown +by code it calls are not checked and will "bump into" the `noexcept( true )` +on `tickingTimeBomb`. As such, the program will terminate, despite the best efforts +of the `cannotTolerateExceptions()` implementation. The language requires a mechanism +to statically check whether callees are equally "responsible" about termination +semantics as the caller. + +To this end, we propose three new mechanisms. + +## Proposal + +We propose a few "extensions" to the `noexcept` keyword used on function declarators. +We will outline these here. + +##### Note about keyword bikeshedding + + +This paper proposes "compound" keywords by recycling the meaning of existing keywords. +The actual keyword or keyword sequences to define these kinds of `noexcept` function are +immaterial to the mechanics of this proposal and thus we defer the decision of keywords +at this time. + +----------------------- + +### First new feature A statically checked `noexcept` function - `static noexcept( true )` + +We propose the introduction of `static noexcept` as both a function declaration +"decorator" and an operator. `static noexcept( true )` or `static noexcept` +tagged functions have similar semantics to `noexcept` tagged or colored functions: + + - An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + - An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + - An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. + - A call to a `static noexcept( true )` function will not throw. + - If an exception unwind (somehow) attempts to "emerge" from a + `static noexcept( true )` function, then the program will terminate. + +However, we propose to add the following behaviors and restrictions to such functions: + + - If a `noexcept( false )` function is called outside of the body of a `try` block, + then the program is ill formed, and a compile-time diagnostic is required. + - If a `noexcept( false )` function is called inside the body of a `try` block, then + a `catch( ... )` block must exist. + - A `catch` block's body is not considered part of a `try` block. + - When checking if a statement is "in" a `try` block, one must walk up the nested + block structure until a try block is reached. A `catch` block attached to a + `try` block which is inside of a try block at broader scope is considered to be + "inside" the broader try block, for the purpose of these rules. + - A simple model of this is that invoking `noexcept( false )` functions is ill formed + unless a `try` block scope can be found by walking outward from the calling scope. + - It is unclear at this time if language UB on expressions should be considered + throwing or not. There are tradeoffs. + - If UB is considered `noexcept( true )`, then there is a potential that UB + can cause program termination by "leaking" an exception out from checked + try blocks. + - If UB is considered `noexcept( false )`, then it makes writing + `static noexcept( true )` extremely difficult. Many situations will + require noisy and defensive + `try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }` + patterns. + +The authors of this paper favor the interpretation that UB and language primitive behavior +should be considered `noexcept( true )` for the purpose of this checking. There already +exists the possibility of "deeper down" time bombs in this construct and language UB +leading to exceptions is a somewhat niche case. + +#### Rewriting the original painful example using this feature + +~~~ +void silentlyThrowing() noexcept( false ) { throw 0; } + +void +tickingTimeBomb() noexcept( true ) +{ + if( not ( rand() % 32 ) ) + { + silentlyThrowing(); + } +} + +enum Result { Success, Failure }; + +Result +illFormedCannotTolerateExceptions() static noexcept( true ) +{ + std::string element; + try + { + element= someVector.at( 42 ); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + someVector.push_back( std::move( element ) ); // This line would be ill formed + tickingTimeBomb(); + + return Success; +} + +Result +cannotTolerateExceptions() static noexcept( true ) +{ + try + { + auto element= someVector.at( 42 ); + + // This line has to be moved into the try block to satisfy the `static noexcept` + // requirements + someVector.push_back( std::move( element ) ); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + tickingTimeBomb(); + + return Success; +} +~~~ + +----------------------- + +### Second new feature: A stronger (strawman) statically checked function - `false static noexcept( true )` + +Because the troublesome `tickingTimeBomb` function, which was marked `noexcept` still +could be called, it's clear that `static noexcept` is insufficiently "sharp" as to catch +all time bombs. Yet is still has utility as a tool to help carefully construct functions +which are more resilient to unexpected termination. That form provides a comfortable middle +ground for many cases. This will become apparent when we introduce our stronger checking mechanisms, +`false static noexcept` and `double static noexcept`. It should be noted that +`false static noexcept` is a "straw man". It appears to be the correct solution, but it +still has traps. We present this one before our real solution, `double static noexcept`. This +solution helps expose and explore the problem space. + +For the `false static noexcept` operator, we propose the following: + + - An evaluation of `noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. + - An evaluation of `static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. + - An evaluation of `false static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. + - An evaluation of `false static noexcept( someStaticNoexceptFunction )` will evaluate as `false`. + - An evaluation of `false static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. + - A call to a `false static noexcept( true )` function will not throw. + - If an exception unwind (somehow) attempts to "emerge" from a + `false static noexcept( true )` function, then the program will terminate. + +However, we propose to add the following behaviors and restrictions to such functions: + + - If a `false static noexcept( false )` function is called outside of the body of a `try` block, + then the program is ill formed, and a compile-time diagnostic is required. + - If a `false static noexcept( false )` function is called inside the body of a `try` block, then + a `catch( ... )` block must exist. + - A `catch` block's body is not considered part of a `try` block. + - When checking if a statement is "in" a `try` block, one must walk up the nested + block structure until a try block is reached. A `catch` block attached to a + `try` block which is inside of a try block at broader scope is considered to be + "inside" the broader try block, for the purpose of these rules. + - A simple model of this is that invoking `false static noexcept( false )` functions is ill formed + unless a `try` block scope can be found by walking outward from the calling scope. + - It is unclear at this time if language UB on expressions should be considered + throwing or not. There are tradeoffs. + - If UB is considered `noexcept( true )`, then there is a potential that UB + can cause program termination by "leaking" an exception out from checked + try blocks. + - If UB is considered `noexcept( false )`, then it makes writing + `static noexcept( true )` extremely difficult. Many situations will + require noisy and defensive + `try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }` + patterns. + +The conclusion about whether language UB should be considered `false static noexcept( true )` +for the purpose of this checking is irrelevant, as this form exists merely for expository +purposes. + +#### Rewriting the original painful example using this feature + +~~~ +void silentlyThrowing() noexcept( false ) { throw 0; } +void wrappedSilentlyThrowing() noexcept( true ) { silentlyThrowing(); } + +// We try to be more responsible +void +tickingTimeBomb() false static noexcept( true ) +try +{ + if( not ( rand() % 32 ) ) + { + silentlyThrowing(); + } + + if( not ( rand() % 32 ) ) + { + wrappedSilentlyThrowing(); + } +} +catch( ... ) +{ +} + +enum Result { Success, Failure }; + +Result +illFormedCannotTolerateExceptions() false static noexcept( true ) +{ + try + { + auto element= someVector.at( 42 ); + + someVector.push_back( std::move( element ) ); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + // This line is ill formed and has to be moved into the try block to satisfy the `false static noexcept` + // requirements + tickingTimeBomb(); + + return Success; +} + +Result +cannotTolerateExceptions() false static noexcept( true ) +{ + try + { + auto element= someVector.at( 42 ); + + someVector.push_back( std::move( element ) ); + + tickingTimeBomb(); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + + return Success; +} +~~~ + + + + + + + + + + + + + + + + + + +=-=-=-=-=-=-=-=-=-=- + + + + + + + + +# `double static noexcept` -- a mechanism for static checking of noexcept blocks + +## Table of Contents + +1. Abstract +2. Discussion +3. Proposal +3. Examples + +## Abstract + +In C++, `noexcept` exists to indicate that a function does not throw an exception. However, +what it actually provides is that a function cannot possibly throw an exception. This is +enforced by "violence", if necessary -- the program will terminate rather than allow +an exception to pass a `noexcept` boundary. Unfortunately, this turns `noexcept` into a +ticking timebomb; any `noexcept( true )` function may unexpectedly call `std::terminate` +at any time, thus aborting a program. This may be undesirable in many different contexts, +from Functional Safety to Program Correctness to Contractual Guarantees. This proposal +provides a mechanism which permits various levels of static guarantees. + +## Discussion + +The `noexcept` facility was added to permit a language level (and type-system level) +facility for promising that a function does not throw and for checking it. However, the +enforcement of this promise is a purely runtime concern. This leads to troublesome +timebombs, such as this code. + +~~~ +void silentlyThrowing() noexcept( false ) { throw 0; } + +void +tickingTimeBomb() noexcept( true ) +{ + if( not ( rand() % 32 ) ) + { + silentlyThrowing(); + } +} + +enum Result { Success, Failure }; + +Result +cannotTolerateExceptions() noexcept( true ) +{ + std::string element; + + try + { + element= someVector.at( 42 ); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + someVector.push_back( std::move( element ) ); + tickingTimeBomb(); + + return Success; +} +~~~ + +While some of the code and functions called in `cannotTolerateExceptions` is capable of +throwing, the caller attempts to resolve exceptions and handle them without program +termination. However, the `tickingTimeBomb` function is a hidden hazard. While +it declares itself to not throw exceptions (which is true), the exceptions thrown +by code it calls are not checked and will "bump into" the `noexcept( true )` +on `tickingTimeBomb`. As such, the program will terminate, despite the best efforts +of the `cannotTolerateExceptions()` implementation. The language requires a mechanism +to statically check whether callees are equally "responsible" about termination +semantics as the caller. + +To this end, we propose three new mechanisms. + +## Proposal + +We propose a few "extensions" to the `noexcept` keyword used on function declarators. +We will outline these here. + +##### Note about keyword bikeshedding + +This paper proposes "compound" keywords by recycling the meaning of existing keywords. +The actual keyword or keyword sequences to define these kinds of `noexcept` function are +immaterial to the mechanics of this proposal and thus we defer the decision of keywords +at this time. + +### A statically checked `noexcept` function - `static noexcept( true )` + +We propose the introduction of `static noexcept` as both a function declaration +"decorator" and an operator. `static noexcept( true )` or `static noexcept` +tagged functions have similar semantics to `noexcept` tagged or colored functions: + + - An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + - An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + - An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. + - A call to a `static noexcept( true )` function will not throw. + - If an exception unwind (somehow) attempts to "emerge" from a + `static noexcept( true )` function, then the program will terminate. + +However, we propose to add the following behaviors and restrictions to such functions: + + - If a `noexcept( false )` function is called outside of the body of a `try` block, + then the program is ill formed, and a compile-time diagnostic is required. + - If a `noexcept( false )` function is called inside the body of a `try` block, then + a `catch( ... )` block must exist. + - A `catch` block's body is not considered part of a `try` block. + - When checking if a statement is "in" a `try` block, one must walk up the nested + block structure until a try block is reached. A `catch` block attached to a + `try` block which is inside of a try block at broader scope is considered to be + "inside" the broader try block, for the purpose of these rules. + - A simple model of this is that invoking `noexcept( false )` functions is ill formed + unless a `try` block scope can be found by walking outward from the calling scope. + - It is unclear at this time if language UB on expressions should be considered + throwing or not. There are tradeoffs. + - If UB is considered `false static noexcept( true )`, then there is a potential that UB + can cause program termination by "leaking" an exception out from checked + try blocks. + - If UB is considered `false static noexcept( false )`, then it makes writing + `false static noexcept( true )` extremely difficult. Many situations will + require noisy and defensive + `try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }` + patterns. + +The conclusion about whether language UB should be considered `false static noexcept( true )` +for the purpose of this checking is irrelevant, as this form exists merely for expository +purposes. + +#### Rewriting the original painful example using this feature + +~~~ +void silentlyThrowing() noexcept( false ) { throw 0; } +void wrappedSilentlyThrowing() noexcept( true ) { silentlyThrowing(); } + +// We try to be more responsible +void +tickingTimeBomb() static noexcept( true ) +try +{ + if( not ( rand() % 32 ) ) + { + silentlyThrowing(); + } + + if( not ( rand() % 32 ) ) + { + wrappedSilentlyThrowing(); + } +} +catch( ... ) +{ +} + +enum Result { Success, Failure }; + +Result +illFormedCannotTolerateExceptions() false static noexcept( true ) +{ + try + { + auto element= someVector.at( 42 ); + + someVector.push_back( std::move( element ) ); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + // This line is ill formed and has to be moved into the try block to satisfy the `false static noexcept` + // requirements + tickingTimeBomb(); + + return Success; +} + +Result +cannotTolerateExceptions() false static noexcept( true ) +{ + try + { + auto element= someVector.at( 42 ); + + someVector.push_back( std::move( element ) ); + + tickingTimeBomb(); + } + catch( ... ) + { + try + { + std::cerr << "Something went wrong" << std::endl; + } + catch( ... ) { /* Failure to log is troublesome but not fatal */ } + return Failure; + } + + + return Success; +} +~~~