Skip to content
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

Extension Methods and delegates problem #1326

Open
MateuszKlatecki opened this issue Jun 22, 2023 · 3 comments
Open

Extension Methods and delegates problem #1326

MateuszKlatecki opened this issue Jun 22, 2023 · 3 comments

Comments

@MateuszKlatecki
Copy link
Member

MateuszKlatecki commented Jun 22, 2023

Target name(s)

ALL

Firmware version

No response

Was working before? On which version?

No response

Device capabilities

No response

Description

For a long time, we have been seeing strange problems at random times, the source of which we could not identify. Weird assertions in Interpreter, or HardFaults in Garbage Collector during ComputeReachabilityGraph, data corruption causing strange exceptions.
Finally managed to extract a fairly short code that causes the problem:

using System;
using System.Threading;

namespace ExtensionMethodAndDelegateProblem
{
    public class Program
    {
        public static void Main()
        {
            Thread.Sleep(5000);

            MyCollection collection = new MyCollection();
            ContainsElements IsNotEmpty = collection.HasElements;
            while (true)
            {
                if (IsNotEmpty())
                {
                    Console.WriteLine($"collection has {collection.Count} elements");
                }
                else
                {
                    Console.WriteLine("collection is empty");
                }
            }
        }
    }

    public delegate bool ContainsElements();

    public class MyCollection
    {
        public int Count => 42;
    }

    public static class MyCollectionExtensions
    {
        public static bool HasElements(this MyCollection collection)
        {
            var upperLimit = collection == null ? 0 : collection.Count;
            return upperLimit > 0;
        }
    }
}

Running this on a real device looks like the first 3 iterations will work and the next one throws an exception:
image

Trying to run it in a Virtual nanoDevice throws a Stack Underflow exception (certainly here: https://github.com/nanoframework/nf-interpreter/blob/main/src/CLR/Core/CLR_RT_StackFrame.cpp#L255)
image.

Adding minor modifications to this code (e.g. adding a thread that stresses GC a lot, or modifying the MyCollection class) and running on real device, causes Hardfaults in GC:
HF in GC

or e.g. asserts in the interpreter.
ldarg0
assign_assert

How to reproduce

Run code provided in description

Expected behaviour

No response

Screenshots

No response

Aditional information

No response

@MateuszKlatecki
Copy link
Member Author

I found that if we don't attach an extension method directly to the delegate but through an indirect static method, everything seems to work fine.
Working code:

public class Program
    {
        static MyCollection collection;
        public static void Main()
        {
            Thread.Sleep(5000);

            collection = new MyCollection();
            ContainsElements IsNotEmpty = Wrapper;
            while (true)
            {
                if (IsNotEmpty())
                {
                    Console.WriteLine($"collection has {collection.Count} elements");
                }
                else
                {
                    Console.WriteLine("collection is empty");
                }
            }
        }

        static bool Wrapper()
        {
            return collection.HasElements();
        }
    }

This may suggest a problem already at the time of assigning the method to the delegate.
At the same time, I'm afraid that the fact that this code with a workaround does not cause problems right away, but it may explode in other situations.

@josesimoes
Copy link
Member

Interesting... that suggests something related with static vs non static methods... maybe...

@MateuszKlatecki
Copy link
Member Author

but extension methods are also static methods that can be called as if they were instance methods of a different type.
For example, static void M(this string s) can be called from any string, such as "abc".M().
Maybe that's where the problem is. When assigning a method to a delegate, it really shouldn't take any parameters, but the extension method takes one parameter.

For example bellow code

public class MyClass {
    
}

public static class MyClassExt
{
    public static bool DoSomething(this MyClass c)
    {
        return false;
    }
}

public static class Program
{
    public static void Main()
    {
        var c = new MyClass();
        
        c.DoSomething();
    }
}

is equivalent to

public class MyClass
{
}

public static class MyClassExt
{
    public static bool DoSomething(MyClass c)
    {
        return false;
    }
}

public static class Program
{
    public static void Main()
    {
        MyClass c = new MyClass();
        MyClassExt.DoSomething(c);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants