Skip to content

SEHException with new Exception behavior in .NET 9 #111242

@kevin-montrose

Description

@kevin-montrose

Description

Starting in .NET 9, pinvoking Lua's luaL_error can raise a SEHException.

I believe this is due to luaL_error being a relatively thin wrapper over longjmp.

I narrowed this down to the newly defaulted on improved exception handling in .NET 9.

Note that while I use KeraLua in my repro, the same thing happens if you pinvoke Lua directly - KeraLua is just a convenient way to package up a pre-built Lua 5.4 binary, and eliminate some of my own pinvoke definition from the repro.

Reproduction Steps

csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="KeraLua" Version="1.4.1" />
  </ItemGroup>

</Project>

Program.cs

using KeraLua;

namespace LongJumpRepro
{
    public class Program
    {
        private const string ErrorMsg = "!!RAISED!!";

        private static Lua? lua;

        public static void Main(string[] args)
        {
            // This works as expected in .NET 8
            // It raises a SEHException in .NET 9
            // The exception in .NET 9 goes away if you set environment variable DOTNET_LegacyExceptionHandling=1

            Console.WriteLine($".NET version: {Environment.Version}");
            Console.WriteLine($"DOTNET_LegacyExceptionHandling: {Environment.GetEnvironmentVariable("DOTNET_LegacyExceptionHandling")}");

            try
            {
                lua = new Lua();
                lua.PushCFunction(RaiseLuaError);
                var status = lua.PCall(0, 0, 0);
                Console.WriteLine($"Status (Expected {LuaStatus.ErrRun}): {status}");

                var errMsg = lua.CheckString(1);
                Console.WriteLine($"Error Message (Expected '{ErrorMsg}'): {ErrorMsg}");
            }
            finally
            {
                lua?.Dispose();
            }
        }

        private static int RaiseLuaError(nint luaState)
        => lua!.Error(ErrorMsg);
    }
}

Expected behavior

Expected behavior is either the .NET 8:
Image
or .NET 9 with DOTNET_LegacyExceptionHandling=1 behavior:
Image

Where luaL_error works when pinvoked.

Actual behavior

A SEHException is raised:
Image

Regression?

Yes, this worked in earlier versions of .NET. I have confirmed it worked in .NET 8, and this was reported when first running the code on .NET 9.

Known Workarounds

Setting DOTNET_LegacyExceptionHandling=1 fixes the issue in .NET 9.

Discussion on the PR that introduced it suggests it is temporary however, if it is removed in a later .NET release we will no longer have a workaround.

Configuration

  • .NET 8 and .NET 9
  • Windows 11
  • x64
  • I have not tested on other configurations
  • I am not using Blazor

Other information

While I admit longjmp is weird, embedding Lua is pretty popular.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions