Type.InvokeMember bug - a small Rotor debugging exercise

I figured I’d go exploring through the Rotor debugger today – we had a bug come in for Type.InvokeMember() where we throw a System.IndexOutOfRangeException unexpectedly. If you’re trying to invoke a method on a class which has an overload that takes “params” args, chances are you’ll run in to it. To firm this up, take a look at the C# file – you’ll notice a params string[] args, and a string arg1, string arg2. What happens if we try to call the method with two arguments?

using System;

using System.Reflection;

public class MainClass

{

    public static void Main(string[] args)

    {

            typeof(MainClass).InvokeMember("MyMethod", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.InvokeMethod, null, null, new object[] { "test", "me" });

      }

      public static void MyMethod(params string[] args)

      {

            foreach (string s in args)

                  Console.WriteLine(s);

      }

      public static void MyMethod(string arg1, string arg2)

      {

            Console.WriteLine(arg1);

            Console.WriteLine(arg2);

      }

}

As it turns out, C# will just call MyMethod(string arg1, string arg2). However, Type.InvokeMember behaves differently. We get an exception:

 

C:\repro>clix invokememberbug.exe

Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

   at System.DefaultBinder.FindMostSpecific(ParameterInfo[] p1, Int32[] paramOrder1, ParameterInfo[] p2, Int32[] paramOrder2, Type[] types, Object[] args)

   at System.DefaultBinder.FindMostSpecificMethod(MethodBase m1, Int32[] paramOrder1, MethodBase m2, Int32[] paramOrder2, Type[] types, Object[] args)

   at System.DefaultBinder.BindToMethod(BindingFlags bindingAttr, MethodBase[] match, Object[]& args, ParameterModifier[] modifiers, CultureInfo cultureInfo, String[] names, Object& state)

   at System.RuntimeType.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParameters)

   at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)

   at MainClass.Main(String[] args) in C:\repro\invokememberbug.cs:line 8

 

IndexOutOfRangeException? That can’t be right. Either it hit the correct method (it’s up for debate which method it should hit), or throw an AmbigousMatchException, like usual. Our preference is for AmbigousMatchException.

 

Well, the object of the blog post is to prove it’s a bug, and not bad client code: lets cook this up in cordbg. We end up throwing the exception here:

 

C:\repro>cordbg clix invokememberbug.exe

Microsoft (R) Shared Source CLI Test Debugger Shell Version 1.0.0003.0

Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

(cordbg) run clix invokememberbug.exe

(cordbg) g

 

DefaultBinder.cs

671: private static int FindMostSpecific(ParameterInfo[] p1, int[] paramOrder1,

672: ParameterInfo[] p2, int[] paramOrder2,

673: Type[] types, Object[] args)

674: {

...

679: if (args != null && args[i] == Type.Missing)

680: continue;

681: Type c1 = p1[paramOrder1[i]].ParameterType;

682:* Type c2 = p2[paramOrder2[i]].ParameterType;

 

Lets check the length and the index:

(cordbg) print p1

p1=(0x00b77e70) array with dims=[2]

  p1[0] = (0x00b77b20) <System.Reflection.ParameterInfo>

  p1[1] = (0x00b77b88) <System.Reflection.ParameterInfo>

(cordbg) print p2

p2=(0x00b77e88) array with dims=[1]

  p2[0] = (0x00b77c70) <System.Reflection.ParameterInfo>

(cordbg) print i

i=0x00000001

(cordbg) print paramOrder1[1]

paramOrder1[1]=0x00000001

(cordbg) print paramOrder2[1]

paramOrder2[1]=0x00000001

(cordbg) print p2[1]

Array index out of range.

Variable unavailable, or not valid

As it turns out, indexing into p2[1] is takes you out of range of the array – there’s our bug. Okay, so we have a bug in the default binder – this is great, my client code should work, it’s the binder that blew up – we’ve found our bug. But why?

 

Turns out that part is a little more tricky. What does FindMostSpecific do? Intuition tells you that it’s probably got a couple of methods parameters, from methods that look similar and it needs to find the best fit (verification of this means looking at the defaultbinder.cs source code, but we’ll keep going…). What is it passing in? Let us look up the stack:

 

(cordbg) up

821: int res = FindMostSpecific(m1.GetParameters(), paramOrder1, m2.GetParameters(), paramOrder2, types, args);

(cordbg) funceval System.Object::ToString m1

Function evaluation complete.

$result=(0x00b8101c) "Void MyMethod(System.String, System.String)"

(cordbg) up

821: int res = FindMostSpecific(m1.GetParameters(), paramOrder1, m2.GetParameters(), paramOrder2, types, args);

(cordbg) funceval System.Object::ToString m2

Function evaluation complete.

$result=(0x00b81084) "Void MyMethod(System.String[])"

We looked up the stack (into FindMostSpecificMethod), found that we were invoking FindMostSpecific with the parameters of two methods, m1 and m2. Surprise surprise. Turns out the code wrapped around FindMostSpecificMethod passes in two methods that could be matches, and asks FindMostSpecificMethod to figure that out. It then calls a helper method FindMostSpecific.

 

It looks like FindMostSpecific relies on the fact that the two methods parameters it has to sort and identify as the most specific, are the same length as the InvokeMember object args list. In this case, MyMethod(params string[] args) is clearly only length 1, and the InvokeMember object array is length 2. Quite simply -  the binder found a couple of matches, asked the helper methods to go figure it out, but the assumption was that the helper methods would always receive a parameter list the same length as the args list. Clearly a bug.

 

Of course, you could dig a little deeper to find out where in the binder the logic exists to collect the methods that look like they could be a target invocation match – but I’ll leave that exercise up to the reader. I hope you caught the general theme – think you’ve found a bug? Okay, go away and take a peek. ;)

 

Have fun

Comments

  • Anonymous
    May 06, 2004
    My apologies if I should have done more research before I contacted Brad with this bug, but I'm not entirely familiar with some of the tools you used to investigate. I looked at the reflector decompilation of System.DefaultBinder to see if it was obvious, but the VS.NET debugger wouldn't step in to that level of depth in mscorlib, and I didn't know what else to do. Thanks for investigating for me.

  • Anonymous
    May 06, 2004
    While we are on the subject, I found another issue (bug or documentation bug) in System.DefaultBinder.

    This one concerns the SelectProperty() method. According to the documentation, the returnType parameter is "The return value the matching property must have." I interpret this to mean that the property type of the matching property must be assignable to returnType. System.DefaultBinder, however, checks to ensure that returnType is assignable to the property type of the matching property.

    I assume this was done so that SelectProperty would work for finding a property to set, but it doesn't match the documentation. Is this a problem with the documentation, or with the code, or should System.Reflection.Binder.SelectProperty have another parameter indicating which type of match is desirable?

  • Anonymous
    June 14, 2004
    I am just guessing here, but I think the bug is caused because the call to

    sscliclrsrcbclsystemruntimetype.cs:451
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    internal extern MethodInfo[] GetMemberMethod(String name,
    BindingFlags invokeAttr,
    CallingConventions callConv,
    Type[] argTypes,
    int argCnt,
    bool verifyAccess);
    from

    sscliclrsrcbclsystemruntimetype.cs:373
    meths = GetMemberMethod(name, invokeAttr, CallingConventions.Any, null, argCnt, false);

    which causes GetMemberMethod to return all methods that will satisfy recieving 2 parameters (argCnt), while the the number of formal parameters itself maybe different.

    Apologies if this is wrong.

  • Anonymous
    November 27, 2007
    PingBack from http://feeds.maxblog.eu/item_1328765.html

  • Anonymous
    June 07, 2009
    PingBack from http://greenteafatburner.info/story.php?id=2315

  • Anonymous
    June 13, 2009
    PingBack from http://quickdietsite.info/story.php?id=1531