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

Problem accessing Linq methods (extension methods of IQueryable) inside isolated code #54

Open
wfrohmberg opened this issue Dec 30, 2024 · 10 comments
Assignees
Labels
bug Something isn't working question/investigation Further information or investigation is required

Comments

@wfrohmberg
Copy link

I belive this problem was already issued before in original tonerdo/pose (tonerdo/pose#11) but I think it remained unaddressed. The problem occures when I want to access e.g. standard collection as queryable from inside of the isolated code e.g.:

internal class Program
{
     public static IQueryable<int> GetInts()
     {
         return null;
     }
     static void Main(string[] args)
     {
         List<int> ints = new List<int>();
         ints.Add(1);
         var getIntsShim = Pose.Shim.Replace(() => Program.GetInts())
             .With(() => ints.AsQueryable());
         Pose.PoseContext.Isolate(() =>
         {
             Console.WriteLine(Program.GetInts().Count());
         }, getIntsShim);
         Console.ReadLine();
     }
}

Isolated code seemed to get null (or at least perceives obtained object as a null).

@Miista
Copy link
Owner

Miista commented Jan 15, 2025

I have looked at it, and it seems that the issue is caused by the call to get_Provider which is placed when a method is executed against an IQueryable. Will investigate further.

UPDATE: I have added tests to verify that we can indeed shim extensions methods. We can. The problem then lies with how the call to get_Provider is rewritten.

UPDATE: Yep. This is caused by an explicit interface implementation. We can fix this by looking up the method from the interface map associated with the type. Then we also need to update how we find the matching shim.

@Miista
Copy link
Owner

Miista commented Jan 16, 2025

@wfrohmberg I pushed a fix for this to the associated branch. This fixes being unable to resolve explicit interface members.

Unfortunately, this is not the whole story. Once I got past the explicit interface, I ran into another problem.

There are the following methods on arrays:

  • GetEnumerator
  • CopyTo
  • get_Count (Count property)
  • get_Item (indexer)
  • set_Item (indexer)
  • Add
  • Contains
  • get_IsReadOnly (IsReadOnly property)
  • Clear
  • IndexOf
  • Insert
  • Remove
  • RemoveAt

However, those methods do not actually exist on array. Arrays in C# "implement" IList, IReadOnlyCollection, and IEnumerable via something called SZArrayHelper. This is a special, magic class which the compiler and the runtime use to make it seem like arrays actually implement the mentioned interfaces. However, that's not true.

This means that when we attempt to devirtualize a method during a rewrite, the method does not exist on the type nor is it implemented via explicit interface.

More info can be found here: https://stackoverflow.com/a/11164210

I will investigate some more and see if there is a solution.

@BigDLA
Copy link

BigDLA commented Jan 20, 2025

Hello, I had simular problem, so I tried using commit https://github.com/Miista/pose/tree/54-problem-accessing-linq-methods-extension-methods-of-iqueryable-inside-isolated-code if it solves problem for us as well. Unfortunatelly I think I ran into simular issue with GetHashCode on IEqualityComparer, as with array methods mentioned above. The situation is this:

  • There is a public static method SomeMethod(IOurInterface type) returning IOurInterface.
  • Inside the method there is call Collection.TryGetValue(type, out IOurInterfaceret), where Collection property is private static readonly ConcurrentDictionary<string, ConcurrentDictionary<IOurInterface, IOurInterface>>.
  • TryGetValue calls GetHashCode.
  • StubHelper.DeVirtualizeMethod then looks for GetHashCode on IOurInterface, where it doesn´t exist, and in GetExplicitlyImplementedMethod it ends in InvalidOperationException because interfaceType is null.

@Miista
Copy link
Owner

Miista commented Jan 21, 2025

@BigDLA Can you put together a small example?

@BigDLA
Copy link

BigDLA commented Jan 21, 2025

I´ll try, but its quite extensive complicated legacy code, so far I wasnt able to replicate it elsewhere. But I will give it another go. :D

@BigDLA
Copy link

BigDLA commented Jan 22, 2025

I beleve I managed to reproduce it: https://github.com/BigDLA/PoseDemo.git, just run the console and it should fail when lolObject.LolMethod() is called in isolated code (specifically on line var foo = CollectionClass.SomeStaticMethod(a))

@Miista
Copy link
Owner

Miista commented Jan 22, 2025

@BigDLA I got past the InvalidOperationException. There are no exceptions. Please see below image.

However, the key doesn't seem to be in the dictionary any longer. I'm sure this is somehow caused by the rewrite.

Image

UPDATE: I was able to get it working successfully if I simply forward calls to ConcurrentDictionary<,> (that is, not rewrite them).

Image

I believe this might be a valid way forward given that ConcurrentDictionary<,> is intended to be used in highly parallel situations.

That said, there seems to be a problem on .NET 6, 7, and 8 with regards to how the IL is rewritten. Most tests fail with "Common Language Runtime detected an invalid program." I'm tracking this is #55

@Miista
Copy link
Owner

Miista commented Jan 23, 2025

@BigDLA It should be fixed in the latest commit on the associated branch. Please verify.

@Miista Miista self-assigned this Jan 23, 2025
@Miista Miista added bug Something isn't working question/investigation Further information or investigation is required labels Jan 23, 2025
@BigDLA
Copy link

BigDLA commented Jan 24, 2025

@Miista It looks like it fixed the reported problem, thank you for the swift reaction. However, I seem to have another problem further down the road, but having trouble pinpointing it. Do you have any tips if it is possible to somehow debug the emmited code?

@Miista
Copy link
Owner

Miista commented Jan 24, 2025

@BigDLA Unfortunately, there is no way to set breakpoints in the emitted code.

You can define a TRACE constant in the Pose project and any project using it directly. This will output the IL for the code under isolation; not the IL being emitted, however.

Putting Console.WriteLines should work, however primitive it might be.

Can you tell me more about the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working question/investigation Further information or investigation is required
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

3 participants