diff --git a/double-static-noexcept.md b/double-static-noexcept.md index f15d47d..4096955 100644 --- a/double-static-noexcept.md +++ b/double-static-noexcept.md @@ -47,6 +47,10 @@ cannotTolerateExceptions() noexcept( true ) try { element= someVector.at( 42 ); + if( someVector.capacity() == someVector.size() ) + { + someVector.reserve( someVector.size() * 2 ); + } } catch( ... ) { @@ -58,6 +62,7 @@ cannotTolerateExceptions() noexcept( true ) return Failure; } + someVector.push_back( std::move( element ) ); tickingTimeBomb(); @@ -65,7 +70,7 @@ cannotTolerateExceptions() noexcept( true ) } ~~~ -While some of the code and functions called in `cannotTolerateExceptions` is capable of +While some of the code and functions called in `cannotTolerateExceptions` are 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 @@ -98,40 +103,40 @@ 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. + 1. An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + 2. An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + 3. An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. + 4. A call to a `static noexcept( true )` function will not throw. + 5. 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. + 1. 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. + 2. If a `noexcept( false )` function is called inside the body of a `try` block, then + a `catch( ... )` block must exist. + 3. A `catch` block's body is not considered part of its `try` block. + 4. 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. + 5. 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. + 6. It is unclear at this time if language UB on expressions should be considered + throwing or not. There are tradeoffs. + 1. 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. + 2. 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 +The authors of this paper favor the interpretation that despite UB, most 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 thus language UB leading to exceptions is a somewhat niche case. #### Rewriting the original painful example using this feature @@ -157,6 +162,10 @@ illFormedCannotTolerateExceptions() static noexcept( true ) try { element= someVector.at( 42 ); + if( someVector.capacity() == someVector.size() ) + { + someVector.reserve( someVector.size() * 2 ); + } } catch( ... ) { @@ -182,7 +191,9 @@ cannotTolerateExceptions() static noexcept( true ) auto element= someVector.at( 42 ); // This line has to be moved into the try block to satisfy the `static noexcept` - // requirements + // requirements. + // + // As such the "somewhat defensive against exceptions" reserve can be eliminated. someVector.push_back( std::move( element ) ); } catch( ... ) @@ -203,12 +214,12 @@ cannotTolerateExceptions() static noexcept( true ) ----------------------- -### Second new feature: A stronger (strawman) statically checked function - `false static noexcept( true )` +### Attempt at a 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 +which are more resilient to unexpected termination. That form (`static noexcept`) 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 @@ -217,38 +228,38 @@ 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. + 1. An evaluation of `noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. + 2. An evaluation of `static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. + 3. An evaluation of `false static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. + 4. An evaluation of `false static noexcept( someStaticNoexceptFunction )` will evaluate as `false`. + 5. An evaluation of `false static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. + 6. A call to a `false static noexcept( true )` function will not throw. + 7. 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. + 1. 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. + 2. If a `false static noexcept( false )` function is called inside the body of a `try` block, then + a `catch( ... )` block must exist. + 3. A `catch` block's body is not considered part of its `try` block. + 4. 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. + 5. 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. + 6. It is unclear at this time if language UB on expressions should be considered + throwing or not. There are tradeoffs. + 1. 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. + 2. 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 @@ -267,46 +278,23 @@ try { if( not ( rand() % 32 ) ) { - silentlyThrowing(); + silentlyThrowing(); // This gets handled in the catch below. } if( not ( rand() % 32 ) ) { + // The rules for `false static noexcept` permit this function to be called, + // despite it being a program termination hazard. wrappedSilentlyThrowing(); } } catch( ... ) { + // Handle unexpected exceptions. } 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 ) { @@ -315,8 +303,6 @@ cannotTolerateExceptions() false static noexcept( true ) auto element= someVector.at( 42 ); someVector.push_back( std::move( element ) ); - - tickingTimeBomb(); } catch( ... ) { @@ -328,11 +314,44 @@ cannotTolerateExceptions() false static noexcept( true ) return Failure; } + // Despite the `false static noexcept` rules claiming that this is safe, + // it is still a ticking timebomb in its own right, and thus for this function, too. + // + // The key take-away here is that despite `false static noexcept`'s recursive enforcement + // rule, we still have termination leaks. + tickingTimeBomb(); return Success; } + ~~~ +----------------------- + +### A second new feature: A static no-termination guarantee -- `do not break if using this` + +All of the major troubles we find with recursive exception safety guarantees bump into a limitation. It is +currently not possible in C++ to know whether a function has a termination hazard. We thus propose +a `do not break if using this` operator and function decorator. + +#### A note on name choice + +While `noterminate` is probably the most obvious name here, there are some concerns with it. Particularly that +`noterminate` implies that it would never terminate or call `std::terminate`. + +#### The `do not break if using this` specification + +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: + + 1. An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + 2. An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`. + 3. An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. + 4. A call to a `static noexcept( true )` function will not throw. + 5. If an exception unwind (somehow) attempts to "emerge" from a + `static noexcept( true )` function, then the program will terminate. +