FastExpressionCompiler icon indicating copy to clipboard operation
FastExpressionCompiler copied to clipboard

The JIT compiler encountered invalid IL code or an internal limitation

Open cda963 opened this issue 1 year ago • 13 comments

I'm using FastExpressionCompiler v2.0.0 along with Mapster (latest version) in a gRPC .Net Core project where I need to convert from a POCO object to a proto object, and everything works just fine.

When I try upgrading FastExpressionCompiler to any version above 2.0.0, the conversion throws an exception with the message "The JIT compiler encountered invalid IL code or an internal limitation".

Example: TenantConfig is my source object (a simple .Net object with a few string/boolean properties), and Protos.TenantConfig is the .proto equivalent of my .Net object:

var failure = new TenantConfig().Adapt<Protos.TenantConfig>();

cda963 avatar May 13 '24 09:05 cda963

@cda963 Please provide the complete code for TenantConfig and Protos.TenantConfig. Every property matters, actually.

dadhi avatar May 13 '24 09:05 dadhi

This is my .Net object

public class TenantConfig
{
	public string Name { get; set; }

	public string Value { get; set; }

	public bool IsEncrypted { get; set; }
}

The Protos.TenantConfig is an auto-generated file based on the the following .proto file:

syntax = "proto3";

option csharp_namespace = "Kq.Protos";

package tenants;

service Tenants
{
	rpc TenantConfig (TenantConfigRequest) returns (TenantConfigResponse);
}

message TenantConfigRequest
{
	string tenantId = 1;
}

message TenantConfigResponse
{
	repeated TenantConfig TenantConfig = 1;
}

message TenantConfig
{
	string id = 1;
	string name = 2;
	string value = 3;
}

Below is the auto-generated file:

using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using Google.Protobuf;
using Google.Protobuf.Reflection;

namespace Kq.Protos;

public sealed class TenantConfig : IMessage<TenantConfig>, IMessage, IEquatable<TenantConfig>, IDeepCloneable<TenantConfig>, IBufferMessage
{
    private static readonly MessageParser<TenantConfig> _parser = new MessageParser<TenantConfig>(() => new TenantConfig());

    private UnknownFieldSet _unknownFields;

    public const int IdFieldNumber = 1;

    private string id_ = "";

    public const int NameFieldNumber = 2;

    private string name_ = "";

    public const int ValueFieldNumber = 3;

    private string value_ = "";

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public static MessageParser<TenantConfig> Parser => _parser;

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public static MessageDescriptor Descriptor => TenantsReflection.Descriptor.MessageTypes[13];

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    MessageDescriptor IMessage.Descriptor => Descriptor;

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public string Id
    {
        get
        {
            return id_;
        }
        set
        {
            id_ = ProtoPreconditions.CheckNotNull(value, "value");
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public string Name
    {
        get
        {
            return name_;
        }
        set
        {
            name_ = ProtoPreconditions.CheckNotNull(value, "value");
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public string Value
    {
        get
        {
            return value_;
        }
        set
        {
            value_ = ProtoPreconditions.CheckNotNull(value, "value");
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public TenantConfig()
    {
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public TenantConfig(TenantConfig other)
        : this()
    {
        id_ = other.id_;
        name_ = other.name_;
        value_ = other.value_;
        _unknownFields = UnknownFieldSet.Clone(other._unknownFields);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public TenantConfig Clone()
    {
        return new TenantConfig(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public override bool Equals(object other)
    {
        return Equals(other as TenantConfig);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public bool Equals(TenantConfig other)
    {
        if (other == null)
        {
            return false;
        }

        if (other == this)
        {
            return true;
        }

        if (Id != other.Id)
        {
            return false;
        }

        if (Name != other.Name)
        {
            return false;
        }

        if (Value != other.Value)
        {
            return false;
        }

        return object.Equals(_unknownFields, other._unknownFields);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public override int GetHashCode()
    {
        int num = 1;
        if (Id.Length != 0)
        {
            num ^= Id.GetHashCode();
        }

        if (Name.Length != 0)
        {
            num ^= Name.GetHashCode();
        }

        if (Value.Length != 0)
        {
            num ^= Value.GetHashCode();
        }

        if (_unknownFields != null)
        {
            num ^= _unknownFields.GetHashCode();
        }

        return num;
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public override string ToString()
    {
        return JsonFormatter.ToDiagnosticString(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public void WriteTo(CodedOutputStream output)
    {
        output.WriteRawMessage(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    void IBufferMessage.InternalWriteTo(ref WriteContext output)
    {
        if (Id.Length != 0)
        {
            output.WriteRawTag(10);
            output.WriteString(Id);
        }

        if (Name.Length != 0)
        {
            output.WriteRawTag(18);
            output.WriteString(Name);
        }

        if (Value.Length != 0)
        {
            output.WriteRawTag(26);
            output.WriteString(Value);
        }

        if (_unknownFields != null)
        {
            _unknownFields.WriteTo(ref output);
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public int CalculateSize()
    {
        int num = 0;
        if (Id.Length != 0)
        {
            num += 1 + CodedOutputStream.ComputeStringSize(Id);
        }

        if (Name.Length != 0)
        {
            num += 1 + CodedOutputStream.ComputeStringSize(Name);
        }

        if (Value.Length != 0)
        {
            num += 1 + CodedOutputStream.ComputeStringSize(Value);
        }

        if (_unknownFields != null)
        {
            num += _unknownFields.CalculateSize();
        }

        return num;
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public void MergeFrom(TenantConfig other)
    {
        if (other != null)
        {
            if (other.Id.Length != 0)
            {
                Id = other.Id;
            }

            if (other.Name.Length != 0)
            {
                Name = other.Name;
            }

            if (other.Value.Length != 0)
            {
                Value = other.Value;
            }

            _unknownFields = UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
        }
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    public void MergeFrom(CodedInputStream input)
    {
        input.ReadRawMessage(this);
    }

    [DebuggerNonUserCode]
    [GeneratedCode("protoc", null)]
    void IBufferMessage.InternalMergeFrom(ref ParseContext input)
    {
        uint num;
        while ((num = input.ReadTag()) != 0)
        {
            switch (num)
            {
                default:
                    _unknownFields = UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
                    break;
                case 10u:
                    Id = input.ReadString();
                    break;
                case 18u:
                    Name = input.ReadString();
                    break;
                case 26u:
                    Value = input.ReadString();
                    break;
            }
        }
    }
}
#if false // Decompilation log
'430' items in cache
------------------
Resolve: 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Found single assembly: 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Runtime.dll'
------------------
Resolve: 'Google.Protobuf, Version=3.23.1.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'
Found single assembly: 'Google.Protobuf, Version=3.25.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'
WARN: Version mismatch. Expected: '3.23.1.0', Got: '3.25.0.0'
Load from: 'C:\Users\Dragos\.nuget\packages\google.protobuf\3.25.0\lib\net5.0\Google.Protobuf.dll'
------------------
Resolve: 'Grpc.Core.Api, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d754f35622e28bad'
Found single assembly: 'Grpc.Core.Api, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d754f35622e28bad'
Load from: 'C:\Users\Dragos\.nuget\packages\grpc.core.api\2.62.0\lib\netstandard2.1\Grpc.Core.Api.dll'
------------------
Resolve: 'System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Found single assembly: 'System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Collections.dll'
------------------
Resolve: 'System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
Found single assembly: 'System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Memory.dll'
------------------
Resolve: 'System.Runtime.InteropServices, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null'
Found single assembly: 'System.Runtime.InteropServices, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
WARN: Version mismatch. Expected: '3.1.0.0', Got: '8.0.0.0'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Runtime.InteropServices.dll'
------------------
Resolve: 'System.Runtime.CompilerServices.Unsafe, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null'
Found single assembly: 'System.Runtime.CompilerServices.Unsafe, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
WARN: Version mismatch. Expected: '3.1.0.0', Got: '8.0.0.0'
Load from: 'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\8.0.3\ref\net8.0\System.Runtime.CompilerServices.Unsafe.dll'
#endif

cda963 avatar May 13 '24 09:05 cda963

Just to double-check. You still have the error with FEC v4.2.0?

dadhi avatar May 13 '24 09:05 dadhi

Yes, I just tried upgrading a couple of hours ago.

cda963 avatar May 13 '24 09:05 cda963

Ok. I will check. The thing is simple so not sure what is going on here.

What are the TargetFrameworks?

dadhi avatar May 13 '24 10:05 dadhi

Thanks.

<TargetFramework>net8.0</TargetFramework>

cda963 avatar May 13 '24 10:05 cda963

Ok, I do see the Id field in the Proto and no IsEncrypted. How do they map? What is Mapster Config for the mapping?

dadhi avatar May 13 '24 11:05 dadhi

Indeed I have changed last night the POCO properties and haven't updated the proto file.. this is what 14hrs work day does to you :). I'll test again and come back if this is still an issue. I apologize for this oversight.


Converting a single object works with v4.2.0, but converting a list of objects doesn't.

	var single = new TenantConfig
	{
		Name = Guid.NewGuid().ToString(),
		Value = Guid.NewGuid().ToString(),
		IsEncrypted = true
	}.Adapt<Protos.TenantConfig>();

	var list = new List<TenantConfig>()
	{
		new() 
		{
			Name = Guid.NewGuid().ToString(),
			Value = Guid.NewGuid().ToString(),
			IsEncrypted = true
		}
	};

	var exceptionHere = list.Adapt<List<Protos.TenantConfig>>();

I can work around this, by creating a list of converted objects.

cda963 avatar May 13 '24 19:05 cda963

Ok, sorry to hear about your 14h wday. Will check the new example.

dadhi avatar May 14 '24 08:05 dadhi

@cda963 I have added the list adapter to test and it is working fine. Check here https://github.com/dadhi/FastExpressionCompiler/commit/0f908c40e98f1e65831b4b6637a5dc0d7b314da1

Maybe I am missing some details?

dadhi avatar May 15 '24 14:05 dadhi

In your test I see var failure = new TenantConfig().Adapt<TenantConfigVal>(); which tests for a simple object conversion, not a list. I couldn't find the part where you test against a list of objects.

cda963 avatar May 15 '24 20:05 cda963

Yep, it is here https://github.com/dadhi/FastExpressionCompiler/blob/912db98743209d5f1f2ccf688886b49a9d970d08/test/FastExpressionCompiler.IssueTests/Issue390_405_406_Mapster_tests.cs#L152

dadhi avatar May 15 '24 20:05 dadhi

Yes, I see it now. The issue I'm facing is in a large solution with many projects. Let me try this in an empty project, and I'll get back to you. Thanks.

cda963 avatar May 15 '24 21:05 cda963

Closing, as it was not updated in quite some time

dadhi avatar Nov 18 '24 22:11 dadhi

Sorry to have not responded on time, but I had to take a break from that project, but I'm back at it again.

In an empty project I can't reproduce the error, but in my solution it still happens and with the latest version (5.3.0) and I've attached a screenshot of where the error seems to occur in FastExpressionCompiler (the value of the "key" parameter appears to be NULL btw).

I hope this sheds some light onto this matter and thanks for the amazing work you've put into this library.

Image

cda963 avatar Oct 26 '25 09:10 cda963

Let's look

dadhi avatar Oct 26 '25 16:10 dadhi