-
Notifications
You must be signed in to change notification settings - Fork 458
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First-chance HandlerException thrown for optional parameters #449
Comments
I wrote an NUnit test action that fails tests that cause first-chance exceptions. It found another place that Windsor is causing and catching exceptions internally in production code, from tests Windsor/src/Castle.Windsor/Core/Internal/TypeUtil.cs Lines 72 to 89 in 6e82f54
I'm getting TypeLoadException every time. These two tests each throw and swallow TypeLoadException twice. Windsor/src/Castle.Windsor/MicroKernel/Handlers/DefaultGenericHandler.cs Lines 363 to 364 in 6e82f54
|
In addition, this is not a first-chance exception in production code, but I discovered that <configuration>
<include uri="include1.xml"/>
<!-- this is also valid -->
<include uri="file://include2.xml"/>
</configuration>
Looks like a bad assumption on the part of whoever wrote |
Here is the source I'm using to make these tests fail: DetectFirstChanceExceptionsAttribute.cs#if !NETCOREAPP1_0
[assembly: CastleTests.DetectFirstChanceExceptions]
namespace CastleTests
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
public sealed class DetectFirstChanceExceptionsAttribute : Attribute, ITestAction
{
private readonly Dictionary<string, List<Exception>> firstChanceExceptionsByTest = new Dictionary<string, List<Exception>>();
public void BeforeTest(ITest test)
{
firstChanceExceptionsByTest.Add(test.Id, new List<Exception>());
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
}
public void AfterTest(ITest test)
{
AppDomain.CurrentDomain.FirstChanceException -= CurrentDomain_FirstChanceException;
var firstChanceExceptions = firstChanceExceptionsByTest[test.Id];
firstChanceExceptionsByTest.Remove(test.Id);
if (firstChanceExceptions.Any())
{
var message = new StringBuilder();
for (var i = 0; i < firstChanceExceptions.Count; i++)
{
message.Append("First-chance exception ").Append(i + 1).Append(" of ").Append(firstChanceExceptions.Count).AppendLine(":");
message.AppendLine(firstChanceExceptions[i].ToString());
message.AppendLine();
}
message.Append("Expected: no first-chance exceptions.");
Assert.Fail(message.ToString());
}
}
// This one neither can be inlined nor can tail call, so it will be in the stack trace if this family of methods is used.
private static readonly MethodInfo AssertThrowsMethod = typeof(Assert).GetMethod(
nameof(Assert.Throws),
BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly,
null,
new[] { typeof(IResolveConstraint), typeof(TestDelegate), typeof(string), typeof(object[]) },
null);
private void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
{
var exceptionTrace = new StackTrace(e.Exception);
if (exceptionTrace.FrameCount != 0
&& exceptionTrace.GetFrame(0).GetMethod().DeclaringType?.Assembly == TestExecutionContext.CurrentContext.CurrentTest.TypeInfo.Assembly)
{
// Ignore exceptions thrown by methods declared in the test project
return;
}
// exceptionTrace doesn’t go far enough back to include the Assert.Throws method
var fullTrace = new StackTrace();
for (var i = 0; i < fullTrace.FrameCount; i++)
{
if (fullTrace.GetFrame(i).GetMethod() == AssertThrowsMethod) return;
}
firstChanceExceptionsByTest[TestContext.CurrentContext.Test.ID].Add(e.Exception);
}
public ActionTargets Targets => ActionTargets.Test;
}
}
#endif |
It's good practice to minimize exception throwing and handling where possible. First-chance exceptions cause the debugger to break when you're debugging some unrelated issue which requires you to turn off Just My Code. In order to get to the exception or breakpoint you care about, you have to skip through many exceptions you don't care about. It can make an otherwise short series of debugging sessions frustrating, especially if the debugger is a bit sluggish.
This is a regular thorn in the side caused by other libraries as well, including the BCL itself. See
https://github.com/dotnet/corefx/issues/19928#issuecomment-330630112
and lower, for instance.Culprit:
Windsor/src/Castle.Windsor/MicroKernel/Resolvers/DefaultDependencyResolver.cs
Lines 427 to 435 in 09751a4
(The
Try
name implies that it is not an exceptional case if the handler cannot be gotten from the kernel, and therefore an exception should not be thrown. Optional parameters are indeed not an exceptional case, so it would be better to follow theTry
pattern here instead of throwing.)Repro code that works outside the debugger, suitable for unit testing:
The text was updated successfully, but these errors were encountered: