Roslyn: System.AccessViolationException when invoking extension method on ValueTuple

Created on 27 Dec 2019  路  9Comments  路  Source: dotnet/roslyn

Issue Title

With .NET Core 3.1.100 we're getting a:
Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
when invoking an extension method on a field of a ValueTuple.

This was fine with .NET Core 3.0.100

General

We're trying to update the Quantum Development Kit runtime from 3.0.100 to 3.1.100 and one of our tests is failing and crashing execution with the exception mentioned above.

I have pinpoint the problem, it happens when we're trying to call an extension method (an extension for object) of a field of a ValueTuple if the field is a generic Type Parameter, for example:

```C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace bug
{
public static class Extension
{
public static String GetValue(this object value) => value?.ToString();
}

public partial class BGen<__T__>
{
    public (Int64, __T__) Data { get; }

    public BGen((Int64, __T__) data)
    {
        this.Data = data;
    }

    internal String Value
    {
        get
        {
            /// This works fine:
            //var i2 = Data.Item2;
            //var q2 = i2?.GetValue();

            // This causes the AccessViolation:
            var q2 = Data.Item2?.GetValue();
            return q2;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var i = new BGen<string>((0, "foo"));
        var r = i.Value;

        Console.WriteLine($"Hello World: {string.Join(',', r)}");
    }
}

}
```

This happens on windows with version 3.1.100. Works fine if downgrading to 3.0.100.

Area-Compilers Bug

Most helpful comment

With relatively current master ...

Assert failure(PID 22924 [0x0000598c], Thread: 44364 [0xad4c]): Assertion failed '!"Bad type in gtNewZeroConNode"' in 'bug.BGen`1[__Canon][System.__Canon]:get_Value():System.String:this' (IL size 41)

    File: C:\repos\runtime1\src\coreclr\src\jit\gentree.cpp Line: 5935
    Image: c:\repos\runtime1\artifacts\tests\coreclr\Windows_NT.x64.Checked\Tests\Core_Root\CoreRun.exe

Wonder if this is bad IL...

  .locals init ([0] valuetype [System.Private.CoreLib]System.ValueTuple`2<int64,!__T__> V_0)
  IL_0000:  ldarg.0
  IL_0001:  call       instance valuetype [System.Private.CoreLib]System.ValueTuple`2<int64,!0> class bug.BGen`1<!__T__>::get_Data()
  IL_0006:  stloc.0
  IL_0007:  ldloca.s   V_0
  IL_0009:  ldflda     !1 valuetype [System.Private.CoreLib]System.ValueTuple`2<int64,!__T__>::Item2
  IL_000e:  ldloc.0
  IL_000f:  box        !__T__
  IL_0014:  brtrue.s   IL_0019
  IL_0016:  pop
  IL_0017:  ldnull
  IL_0018:  ret
  IL_0019:  ldobj      !__T__
  IL_001e:  box        !__T__
  IL_0023:  call       string bug.Extension::GetValue(object)
  IL_0028:  ret
} // end of method BGen`1::get_Value

Here _T_ is __Canon and so a ref type, thus the box becomes a nop, leaving a struct on the stack for brtrue, which causes the assert. Per Ecma-355 the top of stack type needs to be verifier-assignable to the box type, that doesn't seem to be the case here.

All 9 comments

cc @dotnet/jit-contrib

With relatively current master ...

Assert failure(PID 22924 [0x0000598c], Thread: 44364 [0xad4c]): Assertion failed '!"Bad type in gtNewZeroConNode"' in 'bug.BGen`1[__Canon][System.__Canon]:get_Value():System.String:this' (IL size 41)

    File: C:\repos\runtime1\src\coreclr\src\jit\gentree.cpp Line: 5935
    Image: c:\repos\runtime1\artifacts\tests\coreclr\Windows_NT.x64.Checked\Tests\Core_Root\CoreRun.exe

Wonder if this is bad IL...

  .locals init ([0] valuetype [System.Private.CoreLib]System.ValueTuple`2<int64,!__T__> V_0)
  IL_0000:  ldarg.0
  IL_0001:  call       instance valuetype [System.Private.CoreLib]System.ValueTuple`2<int64,!0> class bug.BGen`1<!__T__>::get_Data()
  IL_0006:  stloc.0
  IL_0007:  ldloca.s   V_0
  IL_0009:  ldflda     !1 valuetype [System.Private.CoreLib]System.ValueTuple`2<int64,!__T__>::Item2
  IL_000e:  ldloc.0
  IL_000f:  box        !__T__
  IL_0014:  brtrue.s   IL_0019
  IL_0016:  pop
  IL_0017:  ldnull
  IL_0018:  ret
  IL_0019:  ldobj      !__T__
  IL_001e:  box        !__T__
  IL_0023:  call       string bug.Extension::GetValue(object)
  IL_0028:  ret
} // end of method BGen`1::get_Value

Here _T_ is __Canon and so a ref type, thus the box becomes a nop, leaving a struct on the stack for brtrue, which causes the assert. Per Ecma-355 the top of stack type needs to be verifier-assignable to the box type, that doesn't seem to be the case here.

@jaredpar @agocke This is invalid IL generated by Roslyn. Could you please transfer this issue to the Roslyn repo? (I do not have the rights to do it.)

ref struct may not be used as type arguments or in tuple fields. Could you show us where __Canon is being provided as a type argument? Unless "ref type" means something else here? @anpaz-msft @AndyAyersMS

Here T is actually string.

We informally use "ref type" to mean IL type O or a reference to a heap object (ref class or boxed value class).

The coreclr runtime shares generic instantiations across ref class types, since the values of that type all have the same size and ABI behavior. So in the runtime, string gets mapped to a special shared placeholder type __Canon and that's what the jit sees. But it is really string.

Thanks, that makes a lot of things click. Will investigate.

This bug appears to be introduced by #35979

@RikkiGibson, thanks for investigating.

With this repro it looks like the value of receiverTemp at https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs#L406 is the tuple (Int64, __T__) rather than __T__ Item2, so the optimization in #35979 is using the wrong value.

We should consider reverting the optimization and the corresponding change to VB.

Fix (as opposed to revert at https://github.com/dotnet/roslyn/pull/40878)

Was this page helpful?
0 / 5 - 0 ratings