Goal
Primary goal is the possibility to develop and run programs satisfying fundamental code-analysis quality rule CA1031 (I'm offering to define terms CA1031-compliance, CA1031-compliant, CA1031-compatibility, CA1031-policy etc).
I am proposing to implement two independent features at runtime and/or compilation levels.
Briefly on CA1031
The idea is expressed by Exceptions and Exception Handling (C# Programming Guide)
Do not catch an exception unless you can handle it and leave the application in a known state. If you catch System.Exception, re-throw it using the throw keyword at the end of the catch block.
For people who want to only start digging in to the topic here are some resources:
- CLR Inside Out - Handling Corrupted State Exceptions
- Stackoverflow related thread
- StackExchange related thread
- CLR via C# by Jeffrey Richter
- Problem definition is placed below in Problem definition and more background section
Compilation level
CA1031 is supposed to be followed in most cases, but c# and other languages offer opposite by default.
We could reverse the default behavior of the catch by introducing new keyword swallow:
try
{
}
catch(Exception e) swallow (IOException,SecurityException,WebException)
{
Debug.WriteLine(e.GetType()); //can be any type, for example NullReferenceException
// Following is added by compiler automatically: if (e is not (IOException or SecurityException or WebException)) throw;
}
This feature does not guarantee satisfaction of CA1031, however it reduces the codebase for those who follow CA1031 and helps to start following for those who do not.
To fallback to old behavior LegacyCatchBehaviorAttribute attribute can be applied to method/class/assembly/build.props.
Besides, it can be applied by default only to libraries, but not to executables.
Runtime level
We have already similar solutions in dotnet for ThreadAbortException, StackoverflowException etc.
Thus, this part is about to extend that special behavior to other scenarios (not only to required by the runtime itself).
In order to allow the old behavior this proposal also assumes several runtime modes.
Suggested changes
-
Add runtime modes CA1031Compliant (NotCompliant=default, Compliant, Force)
-
Add assembly attribute (+ compile option, project property etc) CA1031Compliant (true/false=default)
-
Compilation of compliant assembly must inject re-throwing code in such a way that Compliant assembly in NotCompliant runtime mode behaves same way as in Compliant runtime mode.
-
Add special rules of runtime Compliant and Force modes:
- Auto-rethrow exceptions from
catch{}, catch(Exception){}, catch(SystemException){} and catch(ApplicationException){} similar to current implementation of ThreadAbortException
- Add
Exception.NoRethrow<TException>() which instructs runtime not to rethrow in current catch block (attempt to use Exception or SystemException or if actual type is not assignable to provided one throws CS1031CompliancyException)
- Attempt to call NotCompliant assembly from Compliant one in Compliant runtime mode throws an exception
-
Define compiler error “Project Xxx can not compile as CA1031-compliant because it references non-compliant assembly Yyy.dll. Either remove the reference or remove CA1031Compliant attribute/option.
Probably, this makes sense to discuss same time:
- Possibility to catch multiple exceptions by one catch with
catch(Exception1,Exception2,Exception3 (BaseException)ex){ }
- Make constructor of System.Exception protected (same for SystemException and ApplicationException)
- Remove special behavior of ThreadAbortException StackOverflow and others in Compliant and Force runtime modes. Deprecate ResetAbort as NoRethrow does the same (also explicit catch of ThreadAbortException should do the same as well). Make StackOverflowException internal etc
- AppDomain.UnhandledException should not rethrow exceptions explicitly in Compliant and Force modes, it should be allowed to catch exceptions using
Exception.NoRethrow<TException>() mentioned above
- Introduce AsyncException for explicit passing the state in async/await scenarios.
- New operators
async throw exception; and async throw; for easy instantiating the AsyncException
Problem definition and more background
Exceptions in dotnet are surely important and popular way to pass the state among the "programs" in the call stack.
It allows to significantly reduce the codebase and to greatly improve the performance.
Same time, we receive several important requirements where one of them is the codebase to remain generally transparent for the exceptions.
This requirement satisfaction is left on developers and very often does not happen due to various reasons.
For example, as exceptions in most cases are associated with the fault state, we do not consider other (non-fault) states expressed by exceptions as well.
Additionally, very often we do not consider faults which are not even related to the context of our code.
In the following example we are assuming that any exception is the result of database connectivity error.
private bool IsDatabaseAccessible()
{
try
{
_dbConnection.ExecuteProbeRequest();
return true;
}
catch
{
return false;
}
}
Same time runtime throws NullReferenceException when _dbConnection is null while the host expects to receive it for the error report. It is wrongly recognized as a connectivity issue and there will be no any error report for us.
Another example: user requests to throw OperationAbortException which is assumed to hit the catch block of JobManager class upper in the stack. Lets assume that JobManager is responsible to free resources for other jobs.
While the end-user just experiences difficulties to cancel jobs from time to time, this case eventually becomes the divination process for developers.
More on the problem
Other possible implementations
With this proposal I would also consider other options. I am sure that the community has plenty of them.
As an example, we could extend HandleProcessCorruptedStateExceptionsAttribute technique to make general catch to work only for exceptions marked by same attribute as our method.
Another idea could be to analyse the stack trace and determine how much the exception is "native" to current or to calling method.
Relativity and/or reference orders of assemblies could also be considered.
Goal
Primary goal is the possibility to develop and run programs satisfying fundamental code-analysis quality rule CA1031 (I'm offering to define terms CA1031-compliance, CA1031-compliant, CA1031-compatibility, CA1031-policy etc).
I am proposing to implement two independent features at runtime and/or compilation levels.
Briefly on CA1031
The idea is expressed by Exceptions and Exception Handling (C# Programming Guide)
For people who want to only start digging in to the topic here are some resources:
Compilation level
CA1031 is supposed to be followed in most cases, but c# and other languages offer opposite by default.
We could reverse the default behavior of the catch by introducing new keyword swallow:
This feature does not guarantee satisfaction of CA1031, however it reduces the codebase for those who follow CA1031 and helps to start following for those who do not.
To fallback to old behavior LegacyCatchBehaviorAttribute attribute can be applied to method/class/assembly/build.props.
Besides, it can be applied by default only to libraries, but not to executables.
Runtime level
We have already similar solutions in dotnet for ThreadAbortException, StackoverflowException etc.
Thus, this part is about to extend that special behavior to other scenarios (not only to required by the runtime itself).
In order to allow the old behavior this proposal also assumes several runtime modes.
Suggested changes
Add runtime modes CA1031Compliant (NotCompliant=default, Compliant, Force)
Add assembly attribute (+ compile option, project property etc) CA1031Compliant (true/false=default)
Compilation of compliant assembly must inject re-throwing code in such a way that Compliant assembly in NotCompliant runtime mode behaves same way as in Compliant runtime mode.
Add special rules of runtime Compliant and Force modes:
catch{},catch(Exception){},catch(SystemException){}andcatch(ApplicationException){}similar to current implementation of ThreadAbortExceptionException.NoRethrow<TException>()which instructs runtime not to rethrow in current catch block (attempt to use Exception or SystemException or if actual type is not assignable to provided one throws CS1031CompliancyException)Define compiler error “Project Xxx can not compile as CA1031-compliant because it references non-compliant assembly Yyy.dll. Either remove the reference or remove CA1031Compliant attribute/option.
Probably, this makes sense to discuss same time:
catch(Exception1,Exception2,Exception3 (BaseException)ex){ }Exception.NoRethrow<TException>()mentioned aboveasync throw exception;andasync throw;for easy instantiating the AsyncExceptionProblem definition and more background
Exceptions in dotnet are surely important and popular way to pass the state among the "programs" in the call stack.
It allows to significantly reduce the codebase and to greatly improve the performance.
Same time, we receive several important requirements where one of them is the codebase to remain generally transparent for the exceptions.
This requirement satisfaction is left on developers and very often does not happen due to various reasons.
For example, as exceptions in most cases are associated with the fault state, we do not consider other (non-fault) states expressed by exceptions as well.
Additionally, very often we do not consider faults which are not even related to the context of our code.
In the following example we are assuming that any exception is the result of database connectivity error.
Same time runtime throws NullReferenceException when _dbConnection is null while the host expects to receive it for the error report. It is wrongly recognized as a connectivity issue and there will be no any error report for us.
Another example: user requests to throw OperationAbortException which is assumed to hit the catch block of JobManager class upper in the stack. Lets assume that JobManager is responsible to free resources for other jobs.
While the end-user just experiences difficulties to cancel jobs from time to time, this case eventually becomes the divination process for developers.
More on the problem
Other possible implementations
With this proposal I would also consider other options. I am sure that the community has plenty of them.
As an example, we could extend HandleProcessCorruptedStateExceptionsAttribute technique to make general catch to work only for exceptions marked by same attribute as our method.
Another idea could be to analyse the stack trace and determine how much the exception is "native" to current or to calling method.
Relativity and/or reference orders of assemblies could also be considered.