Skip to content

Exception Handling Problem with Null Function Pointer Invocation #115043

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

Closed
Liangjia0411 opened this issue Apr 25, 2025 · 11 comments
Closed

Exception Handling Problem with Null Function Pointer Invocation #115043

Liangjia0411 opened this issue Apr 25, 2025 · 11 comments
Labels
area-ExceptionHandling-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@Liangjia0411
Copy link

Problem Description

In .NET 9, when invoking null function pointers (delegate*), exceptions cannot be properly caught, causing the program to crash directly. Additionally, managed and unmanaged function pointers exhibit inconsistent behaviors:

  1. When invoking a null unmanaged function pointer (delegate* unmanaged<>):

    • Exceptions cannot be caught with try/catch
    • Program crashes, console shows stack trace output but without line numbers
    • Normal exception handling is impossible
  2. When invoking a null managed function pointer (delegate*<>):

    • Exceptions cannot be caught with try/catch
    • Program crashes, console does not show any stack trace output
    • Normal exception handling is impossible

Steps to Reproduce

  1. Create a .NET 9 console application
  2. Add the code below
  3. Run the program

Expected Behavior

When invoking a null function pointer, a catchable exception (such as NullReferenceException) should be thrown, allowing the program to handle the exception normally and continue execution.

Actual Behavior

The program crashes and exceptions cannot be caught. Managed and unmanaged function pointers behave inconsistently (unmanaged pointers at least provide some stack information).

Test Code

class FunctionPointerTest
{
    // Test method
    public static unsafe void Main()
    {
        TestUnmanaged();
        TestManaged();
    }

    private static unsafe void TestUnmanaged()
    {
        Console.WriteLine("Start test delegate* unmanaged...");

        delegate* unmanaged<int, int, int> addPtr = &AddNumbers;

        int sum = addPtr(10, 20);
        Console.WriteLine($"Add Result: {sum}");

        TestUnmanagedNullFunctionPointer();
    }

    [UnmanagedCallersOnly]
    private static unsafe int AddNumbers(int a, int b)
    {
        return a + b;
    }

    private static unsafe void TestUnmanagedNullFunctionPointer()
    {
        Console.WriteLine("Testing unmanaged null function pointer:");

        delegate* unmanaged<int, int, int> nullPtr = null;

        try
        {
            int result = nullPtr(5, 10);
            Console.WriteLine($"Result: {result}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Expected exception: {ex}");
        }
    }

    private static unsafe void TestManaged()
    {
        Console.WriteLine("Start test delegate* managed...");

        delegate*<int, int, int> managedAddPtr = &ManagedAddNumbers;

        int sum = managedAddPtr(10, 20);
        Console.WriteLine($"Add Result: {sum}");

        TestManagedNullFunctionPointer();
    }

    private static int ManagedAddNumbers(int a, int b)
    {
        return a + b;
    }

    private static unsafe void TestManagedNullFunctionPointer()
    {
        Console.WriteLine("Testing managed null function pointer:");

        delegate*<int, int, int> nullPtr = null;

        try
        {
            int result = nullPtr(5, 10);
            Console.WriteLine($"Result: {result}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Expected exception: {ex}");
        }
    }
}
@ghost ghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Apr 25, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Apr 25, 2025
@jkotas
Copy link
Member

jkotas commented Apr 25, 2025

Invoking null function pointer is invalid operation. It is not expected to result into a nice catchable NullReferenceException.

Function pointers are unsafe construct. You have to make sure to use them correctly.

@jkotas jkotas added area-ExceptionHandling-coreclr question Answer questions and provide assistance, not an issue with source code or documentation. and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Apr 25, 2025
@huoyaoyuan
Copy link
Member

Function pointers are unsafe construct. You have to make sure to use them correctly.

Is dereferencing null pointer similarly undefined? In current implementation they are treated the same with managed reference.

@jkotas
Copy link
Member

jkotas commented Apr 25, 2025

Is dereferencing null pointer similarly undefined?

Dereferencing null unmanaged pointer produces deterministic NullReferenceException in CoreCLR. I am not sure whether it is the case across all .NET runtimes (Mono, Unity).

@Liangjia0411
Copy link
Author

Function pointers are unsafe construct. You have to make sure to use them correctly.

Would the recommended best practice be to always perform explicit null checks before invoking function pointers? Something like:

if (somePtr != null)
{
    return somePtr(arg1, arg2);
}
else
{
    // Handle null pointer case
    throw new InvalidOperationException("Function pointer cannot be null");
    // or return default value, etc.
}

@huoyaoyuan
Copy link
Member

Dereferencing null unmanaged pointer produces deterministic NullReferenceException in CoreCLR. I am not sure whether it is the case across all .NET runtimes (Mono, Unity).

I'd like to check specification about it. Even if undefined, producing deterministic NullReferenceException for function pointer is helpful for debugging.

@jkotas
Copy link
Member

jkotas commented Apr 25, 2025

Would the recommended best practice be to always perform explicit null checks before invoking function pointers?

I would expect well-written unsafe code to be tight and avoid these kind of error conditions by construction. I think Debug.Assert would be more useful as debugging aid.

If your unsafe code ends up with calling null function pointers unexpectedly, you may have more fundamental problems with memory safety. There may be situations where InvalidOperationException like you have shown is appropriate, but I would expect those situations to be very rare.

@jkotas
Copy link
Member

jkotas commented Apr 25, 2025

Even if undefined, producing deterministic NullReferenceException for function pointer is helpful for debugging.

It is not possible to produce deterministic NullReferenceException for null function pointer calls, without degrading performance of function pointer calls, or without compromising security/diagnosability.

It may be possible to improve debugging experience for null function pointer calls under Visual Studio debugger. If you care about that, it should be filled using VS feedback.

@colejohnson66
Copy link

colejohnson66 commented Apr 25, 2025

Is there any meaningful difference between calling a function at 0 and reading from 0? I’d assume the only difference is the selector used (CS vs DS).

@jkotas
Copy link
Member

jkotas commented Apr 25, 2025

The observable behavior is different. When you are calling a function at 0, you won't get the fault at the call instruction. You will get a fault from trying to execute code at address 0.

@MichalPetryka
Copy link
Contributor

The observable behavior is different. When you are calling a function at 0, you won't get the fault at the call instruction. You will get a fault from trying to execute code at address 0.

Can't that be handled by lookup of the return address in such case?

@jkotas
Copy link
Member

jkotas commented Apr 26, 2025

If the call was optimized into a tailcall, the return address will point to code that has nothing to do with the null reference exception.

#115043 (comment)

@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Apr 26, 2025
@github-actions github-actions bot locked and limited conversation to collaborators May 27, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-ExceptionHandling-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

5 participants