Skip to content

Adding exception filter infrastructure to the BCL for WebAssembly #34921

@kg

Description

@kg

I'm working on an exception filter emulation system for the WebAssembly target due to the fact that filters can't be implemented the normal way on this target. Part of this necessitates adding some support code to mscorlib, because we now have BCL classes that use exception filters. I'd like to do a first pass design review on the support code so that it will be easier to land it once it's ready.

You can see the current draft version of it here: https://github.com/kg/ExceptionRewriter/blob/master/ExceptionFilterSupport/ExceptionFilter.cs
The basic model is that the filter emulator creates a derived type of ExceptionFilter for each unique exception filter, and the Evaluate method evaluates the filter for a given exception. The statics are used to maintain a stack of active filters and then walk it when processing an exception. Because WASM doesn't give us any stackwalking or unwinding capability, we have to evaluate all the filters on the stack at first opportunity and store the results so that we can use the results as the unwind actually proceeds.

I'll be cleaning it up some before attempting to land, but it'd be great to review the overall design and see if there are changes the team would like to see before adding it to corlib. The rewriter itself will be moving into the linker once it's done, but general feedback on the approach there too is welcome if you want to offer it - I'm mostly looking for feedback on the corlib bits right now.

Here's an example of what the emulator does to a method with a filter in it (this is decompiler output).

before:

// ExceptionTransformTests.C
// Token: 0x06000016 RID: 22 RVA: 0x0000296C File Offset: 0x00000B6C
public void TestReturns()
{
	int i = 0;
	try
	{
		i += C.One(true);
	}
	catch (Exception exc) when (C.ExceptionFilter(exc))
	{
		Console.WriteLine("Layer 1");
		i += C.One(true);
	}
}

after:

// ExceptionTransformTests.C
// Token: 0x0600004F RID: 79 RVA: 0x00003564 File Offset: 0x00001764
public void TestReturns()
{
	C.TestReturns__closure10 testReturns__closure = new C.TestReturns__closure10();
	C.TestReturns__closure10.set_i(0, testReturns__closure);
	C.TestReturns__filter17 testReturns__filter;
	try
	{
		if (testReturns__filter == null)
		{
			testReturns__filter = new C.TestReturns__filter17();
			testReturns__filter.closure = testReturns__closure;
		}
		ExceptionFilter.Push((ExceptionFilter)testReturns__filter);
		try
		{
			C.TestReturns__closure10.set_i(testReturns__closure.i + C.One(true), testReturns__closure);
		}
		catch (object obj)
		{
			if (((ExceptionFilter)testReturns__filter).ShouldRunHandler(obj))
			{
				switch (C.TestReturns__catch24(obj, testReturns__closure))
				{
				default:
					throw;
				case 1:
					break;
				}
			}
		}
	}
	finally
	{
		ExceptionFilter.Pop((ExceptionFilter)testReturns__filter);
	}
}

// Token: 0x02000020 RID: 32
public class TestReturns__filter17 : ExceptionFilter
{
	// Token: 0x06000077 RID: 119 RVA: 0x00003E98 File Offset: 0x00002098
	public override int Evaluate(object exc)
	{
		C.TestReturns__closure10 testReturns__closure = this.closure;
		Exception ex = exc as Exception;
		return (ex != null && C.ExceptionFilter(ex) > false) ? 1 : 0;
	}

	// Token: 0x04000028 RID: 40
	public C.TestReturns__closure10 closure;
}

cc @lewing

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions