runtime: System.Text.Json Access Violation Exception (Attempted to read or write protected memory)
Description
First just a quick bit of background: as I’ve raised in comments in #36621, I’ve hit a variety of InvalidProgramException
and AccessViolationException
while trying to bring System.Text.Json (Previews 4 through 7) to Schema.NET (https://github.com/RehanSaeed/Schema.NET/pull/100). Prior to trying System.Text.Json v5 previews, I had tried v4 which worked with my converter without this exception (though I really wanted the setting to not serialize default values which is coming in v5).
Jumping to now with Preview 7, I’m hitting issues with an AccessViolationException
with the error message:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I’ve created a custom solution with as little code as I reasonably could while still using a custom version of the core from Schema.NET: https://github.com/Turnerj/SchemaNet_SystemTextJson
public class CustomType : Thing, IThing
{
public override string Type => "CustomType";
[JsonPropertyName("name")]
[JsonConverter(typeof(ValuesJsonConverter))]
public OneOrMany<string> Name { get; set; }
[JsonPropertyName("uri")]
[JsonConverter(typeof(ValuesJsonConverter))]
public Values<string, Uri> Uri { get; set; }
}
class Program
{
static void Main(string[] args)
{
// This is the JSON of the object below
// {"@context":"https://schema.org","@type":"CustomType","name":"Hello World"}
var inputObj = new CustomType
{
Name = "Hello World"
};
var json = SchemaSerializer.SerializeObject(inputObj); // Exception occurs on serialization
var outputObj = SchemaSerializer.DeserializeObject<CustomType>(json);
}
}
Things to note:
OneOrMany
andValues
are both structs which will serialize to a single item or an arrayValues
is special because it can convert to and from any of the generic types specified on itValuesJsonConverter
does the guts of the converting. In debugging, theCanConvert
method is hit howeverWrite
is never actually being called on it.- The
ValuesJsonConverter
’sCanConvert
is:public override bool CanConvert(Type typeToConvert) => typeof(IValues).IsAssignableFrom(typeToConvert);
Configuration
- .NET Core 3.1
- System.Text.Json 5.0.0-preview.7.20364.11
- Windows 10 Home (10.0.18362 Build 18362)
- I’ve hit this issue in CI builds on this pull request for Ubuntu, Mac and Windows
Regression?
As mentioned, I didn’t have this issue with System.Text.Json v4 (likely the last minor version) though I needed v5 as I needed the “don’t serialize default values” configuration.
Other information
Full Exception
System.AccessViolationException: ‘Attempted to read or write protected memory. This is often an indication that other memory is corrupt.’ at DynamicClass.get_Name(System.Object) at System.Text.Json.JsonPropertyInfo
1[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetMemberAndWriteJson(System.Object, System.Text.Json.WriteStack ByRef, System.Text.Json.Utf8JsonWriter) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter
1[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef) at System.Text.Json.Serialization.JsonConverter1[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef) at System.Text.Json.Serialization.JsonConverter
1[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef) at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCoreAsObject(System.Text.Json.Utf8JsonWriter, System.Object, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef) at System.Text.Json.JsonSerializer.WriteCore[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Serialization.JsonConverter, System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef) at System.Text.Json.JsonSerializer.WriteCore[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Type, System.Text.Json.JsonSerializerOptions) at System.Text.Json.JsonSerializer.Serialize[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.__Canon ByRef, System.Type, System.Text.Json.JsonSerializerOptions) at System.Text.Json.JsonSerializer.Serialize[[System.__Canon, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.__Canon, System.Text.Json.JsonSerializerOptions) at Schema.NET.SchemaSerializer.SerializeObject(System.Object, System.Text.Json.JsonSerializerOptions) at Schema.NET.SchemaSerializer.SerializeObject(System.Object) at SchemaNet_SystemTextJson.Program.Main(System.String[])
Additionally, when I first went to Preview 4, while I didn’t hit this exception, I did hit a CLR exception (“Common Language Runtime detected an invalid program”).
While this issue is happening with serialization, I have experienced similar issues with deserialization too when running CI builds of the Schema.NET PR I’ve linked to a few times. I’m thinking the fix for serialization might fix the deserialization issue too.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 1
- Comments: 17 (12 by maintainers)
Hi @Turnerj root cause of this issue is that you have a single
JsonConverter<IValues>
and the implementations of that interface are value types. That means the property values have to be boxed while serializing and unboxed while deserializing.For performance reasons, STJ generates getter/setter in plain IL and explicitly without boxing. As a result, in this case the generated IL is invalid because the values (say an Int32) are treated as pointer to an object. The
AccessViolationException
is just one possible consequence - I tested some scenarios and observed scary effects.In #40914 the missing box/unbox IL is emitted, which fixes the problem, but I assume there are performance concerns and it will not be merged - at least in .NET 5. As a workaround you can create a
JsonConverter<T>
for each implementation type like in this PR.@layomia Please let me know what you think about the boxing in #40914. If that is not possible, there should a
NotSupportedException
get thrown in this case. Fortunately the runtime is pretty robust but aAccessViolationException
orInvalidProgramException
can be frightening 😉Thanks @Turnerj. Yes, the fix will come in 5.0 RC1. Please let us know how things go with the fix.
Fixed for 5.0 in https://github.com/dotnet/runtime/pull/41366.
Re-opening until the fix is back-ported to .NET 5 - https://github.com/dotnet/runtime/pull/41366.
@Turnerj, thanks for the detailed repro.
@devsko, thanks for digging into this. Yes, please open a PR and I’ll take a look.
I had a look into the
ReflectionEmitMemberAccessor
that causes the problem in the description. The dynamically generated getter is of typeIValues
but leaves anOneOrMany<string>
on the stack. Clearely this must go horrible wrong without boxing - what it does. It seems an additionalgenerator.Emit(OpCodes.Box, typeof(OneOrMany<string>));
solves the problem for this case.Tested with few alternatives (value types included) and it is throwing EntryPointNotFoundException on macOS for all T in
OneOnMany<T>
, withnetcoreapp3.1, OOTB preview 7 S.T.J
andnet5 preview 8 inbox S.T.J
. I have also seen it in other cases where AccessViolationException on Windows for the exact same code is something else on Unix-like OS with CoreCLR.Maybe because we are using different operating system (Windows, macOS)?
Btw, this mysterious EntryPointNotFoundException due to usage of generic structs has also been reported to roslyn repo few times: https://github.com/dotnet/roslyn/search?q=EntryPointNotFoundException&type=Issues. A self-contained repro is bit tricky but would be great to nib the bug.