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

System.MissingMethodException: Method not found: 'Void System.Text.Json.Serialization.Metadata.JsonObjectInfoValues1.set_ObjectCreator(System.Func1<!0>)'. with .NET Standard 2.0 #61737

Closed
hugoqribeiro opened this issue Nov 17, 2021 · 13 comments · Fixed by #63472
Assignees
Milestone

Comments

@hugoqribeiro
Copy link

hugoqribeiro commented Nov 17, 2021

Description

I have a .NET 6 app that references a .NET Standard 2.0 class library, which in turn uses the new System.Text.Json source generator to speed up serialization.

When running I get the following exception (this stack trace is from a project used to reproduce the error):

System.MissingMethodException
  HResult=0x80131513
  Message=Method not found: 'Void System.Text.Json.Serialization.Metadata.JsonObjectInfoValues`1.set_ObjectCreator(System.Func`1<!0>)'.
  Source=ClassLibrary1
  StackTrace:
   at ClassLibrary1.SerializerContext.get_MyModel() in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\System.Text.Json.SourceGeneration\System.Text.Json.SourceGeneration.JsonSourceGenerator\SerializerContext.MyModel.g.cs:line 37
   at ClassLibrary1.Serializer.Serialize(MyModel model) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\Serializer.cs:line 9
   at Program.<Main>$(String[] args) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ConsoleApp1\Program.cs:line 5

This DOES NOT happen if the class library targets .NET 6.

Reproduction Steps

I've created a sample to reproduce this so I could eliminate the possibility of something being wrong with the original projects (upgraded from .NET 5).

The class library (netstandard2.0) code:

namespace ClassLibrary1
{
    public class MyModel
    {
        public string Value
        {
            get; set;
        }
    }

    public partial class Serializer
    {
        public static string Serialize(MyModel model)
        {
            return JsonSerializer.Serialize(
                model,
                SerializerContext.Default.MyModel);
        }

        public static string SerializeWithGenerics<T>(T model)
        {
            return JsonSerializer.Serialize(
                model,
                typeof(T),
                SerializerContext.Default);
        }
    }

    [JsonSerializable(typeof(MyModel))]
    internal partial class SerializerContext : JsonSerializerContext
    {
    }
}

The console app (net6.0) code:

using ClassLibrary1;

MyModel model1 = new MyModel();

string json1 = Serializer.Serialize(model1);
string json2 = Serializer.SerializeWithGenerics(model1);

Console.WriteLine(json1);
Console.WriteLine(json2);
Console.ReadKey();

Expected behavior

This should work exactly the same in .NET 6 and .NET Standard 2.0, given that the System.Text.Json is multi-targeting.

Actual behavior

It fails in .NET Standard 2.0 with the exception above.

Regression?

No response

Known Workarounds

No response

Configuration

.NET 6.0.100
Visual Studio 2022 17.0.1
Windows 10 20H2

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Nov 17, 2021
@ghost
Copy link

ghost commented Nov 17, 2021

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

I have a .NET 6 app that references a .NET Standard 2.0 class library, which in turn uses the new System.Text.Json source generator to speed up serialization.

When running I get the following exception (this stack trace is from a project used to reproduce the error):

System.MissingMethodException
  HResult=0x80131513
  Message=Method not found: 'Void System.Text.Json.Serialization.Metadata.JsonObjectInfoValues`1.set_ObjectCreator(System.Func`1<!0>)'.
  Source=ClassLibrary1
  StackTrace:
   at ClassLibrary1.SerializerContext.get_MyModel() in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\System.Text.Json.SourceGeneration\System.Text.Json.SourceGeneration.JsonSourceGenerator\SerializerContext.MyModel.g.cs:line 37
   at ClassLibrary1.Serializer.Serialize(MyModel model) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\Serializer.cs:line 9
   at Program.<Main>$(String[] args) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ConsoleApp1\Program.cs:line 5

This DOES not happen if the class library targets .NET 6.

Reproduction Steps

I've created a sample to reproduce this so I could eliminate the possibility of something being wrong with the original projects (upgraded from .NET 5).

The class library (netstandard2.0) code:

namespace ClassLibrary1
{
    public class MyModel
    {
        public string Value
        {
            get; set;
        }
    }

    public partial class Serializer
    {
        public static string Serialize(MyModel model)
        {
            return JsonSerializer.Serialize(
                model,
                SerializerContext.Default.MyModel);
        }

        public static string SerializeWithGenerics<T>(T model)
        {
            return JsonSerializer.Serialize(
                model,
                typeof(T),
                SerializerContext.Default);
        }
    }

    [JsonSerializable(typeof(MyModel))]
    internal partial class SerializerContext : JsonSerializerContext
    {
    }
}

The console app (net6.0) code:

using ClassLibrary1;

MyModel model1 = new MyModel();

string json1 = Serializer.Serialize(model1);
string json2 = Serializer.SerializeWithGenerics(model1);

Console.WriteLine(json1);
Console.WriteLine(json2);
Console.ReadKey();

Expected behavior

This should work exactly the same in .NET 6 and .NET Standard 2.0, given that the System.Text.Json is multi-targeting.

Actual behavior

It fails in .NET Standard 2.0 with the exception above.

Regression?

No response

Known Workarounds

No response

Configuration

.NET 6.0.100
Visual Studio 2022 17.0.1
Windows 10 20H2

Other information

No response

Author: hugoqribeiro
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

ericstj added a commit to ericstj/sample-code that referenced this issue Nov 17, 2021
@ericstj
Copy link
Member

ericstj commented Nov 17, 2021

Can you share the repro project? I tried to repro and it worked for me: https://github.com/ericstj/sample-code/tree/jsonRepro61737/jsonRepro61737

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Nov 17, 2021
@ghost
Copy link

ghost commented Nov 17, 2021

This issue has been marked needs more info since it may be missing important information needed to assess it. Please refer to our contribution guidelines for tips on how to report issues effectively.

@hugoqribeiro
Copy link
Author

@ericstj yours is working because the client lib targets .net 6, not .net standard 2.0.

See here please: https://github.com/hugoqribeiro/trial-n-error/tree/main/System.Text.Json.Issue.61737

@ghost ghost added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed needs more info labels Nov 18, 2021
@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Nov 19, 2021

I can reproduce -- see here for a minimal repro without sourcegen. The issue reproduces with net6.0 console apps only, it works fine in netcoreapp3.1 and net5.0.

This seems related to exposing init-only properties in netstandard2.0 TFMs. When ClassLibrary1 in the repro is compiled as a net6.0 artifact, the setter method is called as follows:

IL_0007: callvirt instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) class set_ObjectCreator

whereas in the netstadard2.0 build it's

IL_0007: callvirt instance void modreq([System.Text.Json]System.Runtime.CompilerServices.IsExternalInit) class set_ObjectCreator

I suspect that the runtime cannot bind to the right method because the modifier types don't match. net5.0 and netcoreapp3.1 apps don't suffer from the issue because they're consuming the netcoreapp3.1 build of System.Text.Json, which also embeds its own IsExternalInit.

Related to dotnet/roslyn#45510

@ericstj
Copy link
Member

ericstj commented Nov 19, 2021

yours is working because the client lib targets .net 6, not .net standard 2.0

Oops, my apologies. Further illustration that repro projects are good to share so we can see the problem ;) Excellent find by the way, this is a very interesting and subtle bug.

whereas in the netstadard2.0 build it's ... [System.Text.Json]System.Runtime.CompilerServices.IsExternalInit

Interesting. @eiriktsarpalis could you try explicitly adding a type-forward in the 6.0 build of System.Text.Json for this type and see if it resolves the issue? It's curious that the compiler leaks knowledge of this internal type to customer assembly. That feels like a violation of the visibility contract (and might require us to add a check for this to our API compat infra cc @safern).

internal
#endif
static class IsExternalInit
{
}

I'd be curious what @jaredpar thinks about the compiler emitting a reference to an internal type here.

@safern
Copy link
Member

safern commented Nov 19, 2021

This is an interesting scenario, that we should definitely explore guarding on API Compat.

@ericstj
Copy link
Member

ericstj commented Nov 19, 2021

Pending @jaredpar's comment on the validity of compiler behavior here, I could imagine an effective visibility rule in API Compat as:
if public method has a modreq with a reference to an internal type, treat that type as if it were public, and make sure it's satisfied in compatible assemblies. Perhaps another version of the rule: treat the types referenced by modreqs as part of the signature that must be satisfied in compatible assemblies. I can imagine this might get a little dicey in our current API compat impl that plays it loose with type-names vs explicit type equivalence.

@jaredpar
Copy link
Member

if public method has a modreq with a reference to an internal type, treat that type as if it were public, and make sure it's satisfied in compatible assemblies

This is a hole we missed with init properties. We did not enforce that the accessibility matched the property. By the time we noticed it was too late, too many people had taken a dependency and it was impossible to make it an error.

@eiriktsarpalis eiriktsarpalis added this to the 6.0.x milestone Nov 22, 2021
@eiriktsarpalis eiriktsarpalis removed the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Nov 22, 2021
@eiriktsarpalis eiriktsarpalis self-assigned this Nov 22, 2021
@ChrisTorng
Copy link

I don't quite understand the details.

I've make a repro to test the .NET Standard 2.0 JsonContext running in .NET 6.0 and .NET Framework 4.8 test project. The result is strange to me.

NetStandard20JsonContext passed in NetFramework48PersonTests, but not in Net60PersonTests.
Net60JsonContext passed in Net60PersonTests.

For the clue of init, I try to add IsExternalInit NuGet package into NetStandard20 project, but have no luck.

The result means I need to add .Net Standard 2.0 JsonContext for .NET Framework 4.8 project. And also add .NET 6.0 JsonContext for .NET 6.0 project. I can't find a way to make the two JsonContexts merged.

@ChrisTorng
Copy link

I've changed my repro to make sure the NetStandard20 project can surely supports init properties after installing IsExternalInit.
And then I change NetStandard20 project to multi-target to netstandard2.0;net6.0. All tests are passed. But I still prefer not to go multi-targeted.

@ericstj ericstj assigned ericstj and unassigned eiriktsarpalis Jan 6, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jan 6, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 7, 2022
@ericstj
Copy link
Member

ericstj commented Jan 7, 2022

Reopen for servicing

@ericstj ericstj reopened this Jan 7, 2022
@ghost ghost added in-pr There is an active PR which will close this issue when it is merged and removed in-pr There is an active PR which will close this issue when it is merged labels Jan 7, 2022
@eiriktsarpalis
Copy link
Member

Addressed in 6.0 by #63520.

@ghost ghost locked as resolved and limited conversation to collaborators Feb 10, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants