Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/HeapDump/GCHeapDump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ private GCHeapDump(Deserializer deserializer)
deserializer.RegisterFactory(typeof(JSHeapInfo), delegate () { return new JSHeapInfo(); });
deserializer.RegisterFactory(typeof(DotNetHeapInfo), delegate () { return new DotNetHeapInfo(); });

// Handle backward compatibility for renamed types
deserializer.OnUnregisteredType = (typeName) =>
{
// SerializationConfiguration was renamed to SerializationSettings in newer versions
if (typeName == "FastSerialization.SerializationConfiguration")
{
// Return a compatible empty placeholder that implements IFastSerializable
return () => new SerializationConfigurationShim();
}
return null;
};

try
{
var entryObj = (GCHeapDump)deserializer.GetEntryObject();
Expand Down Expand Up @@ -1087,3 +1099,22 @@ private static float FetchFloat(XmlReader reader, string attributeName, float de

}

/// <summary>
/// Compatibility shim for the old SerializationConfiguration class that was renamed to SerializationSettings.
/// This class provides backward compatibility when deserializing old .gcdump files that reference
/// the old "FastSerialization.SerializationConfiguration" type name.
/// </summary>
internal class SerializationConfigurationShim : IFastSerializable
{
public void ToStream(Serializer serializer)
{
// Empty implementation - this is just a compatibility shim
}

public void FromStream(Deserializer deserializer)
{
// Empty implementation - this is just a compatibility shim that consumes
// any data that might have been serialized with the old type
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,44 @@ public void FailToDeserializeUnregisteredType()
Assert.Throws<TypeLoadException>(() => d.ReadObject());
}

[Fact]
public void SerializationConfigurationCompatibilityMapping()
{
SerializationSettings settings = SerializationSettings.Default;

// Create a simple serializable object
SampleSerializableType sample = new SampleSerializableType(SampleSerializableType.ConstantValue);
MemoryStream ms = new MemoryStream();
Serializer s = new Serializer(new IOStreamStreamWriter(ms, settings, leaveOpen: true), sample);
s.Dispose();

// Test the OnUnregisteredType callback for backward compatibility
Deserializer d = new Deserializer(new PinnedStreamReader(ms, settings), "name");
d.RegisterType(typeof(SampleSerializableType));

// Set up OnUnregisteredType handler similar to what GCHeapDump does
d.OnUnregisteredType = (typeName) =>
{
if (typeName == "FastSerialization.SerializationConfiguration")
{
// Return a factory for a compatible dummy object
return () => new SerializationConfigurationCompatibilityShim();
}
return null;
};

// This should succeed and not throw a TypeLoadException for SerializationConfiguration
SampleSerializableType deserialized = (SampleSerializableType)d.ReadObject();
Assert.Equal(SampleSerializableType.ConstantValue, deserialized.BeforeValue);
Assert.Equal(SampleSerializableType.ConstantValue, deserialized.AfterValue);

// Test that the callback correctly handles the old type name
var factory = d.OnUnregisteredType("FastSerialization.SerializationConfiguration");
Assert.NotNull(factory);
var shimInstance = factory();
Assert.IsType<SerializationConfigurationCompatibilityShim>(shimInstance);
}

[Fact]
public void SuccessfullyDeserializeRegisteredType()
{
Expand Down Expand Up @@ -157,4 +195,20 @@ void IFastSerializable.FromStream(Deserializer deserializer)
AfterValue = deserializer.ReadInt();
}
}

/// <summary>
/// Test compatibility shim for SerializationConfiguration backward compatibility.
/// </summary>
public sealed class SerializationConfigurationCompatibilityShim : IFastSerializable
{
void IFastSerializable.ToStream(Serializer serializer)
{
// Empty implementation - this is just a compatibility shim
}

void IFastSerializable.FromStream(Deserializer deserializer)
{
// Empty implementation - this is just a compatibility shim
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using FastSerialization;
using System.IO;
using Xunit;

namespace TraceEventTests
{
public class GCHeapDumpCompatibilityTests
{
[Fact]
public void GCHeapDumpHandlesSerializationConfigurationCompatibility()
{
// This test verifies that the GCHeapDump deserializer can handle
// references to the old "FastSerialization.SerializationConfiguration" type
// by mapping it to a compatible shim.

var settings = SerializationSettings.Default;

// Create a simple object and serialize it first to have valid stream data
var testObject = new SampleSerializableType(42);
var stream = new MemoryStream();
var serializer = new Serializer(new IOStreamStreamWriter(stream, settings, leaveOpen: true), testObject);
serializer.Dispose();

// Create a deserializer similar to what GCHeapDump does
var deserializer = new Deserializer(new PinnedStreamReader(stream, settings), "test");

// Set up the factories similar to what GCHeapDump constructor does
deserializer.RegisterFactory(typeof(SampleSerializableType), () => new SampleSerializableType());

// Set up the compatibility mapping like our fix does
deserializer.OnUnregisteredType = (typeName) =>
{
if (typeName == "FastSerialization.SerializationConfiguration")
{
return () => new SerializationConfigurationCompatibilityShim();
}
return null;
};

// Test that the callback correctly handles the old type name
var factory = deserializer.OnUnregisteredType("FastSerialization.SerializationConfiguration");
Assert.NotNull(factory);

var shimInstance = factory();
Assert.NotNull(shimInstance);
Assert.IsType<SerializationConfigurationCompatibilityShim>(shimInstance);

// Test that unrelated type names return null (default behavior)
var unknownFactory = deserializer.OnUnregisteredType("Some.Unknown.Type");
Assert.Null(unknownFactory);
}

[Fact]
public void SerializationConfigurationShimCanBeSerializedAndDeserialized()
{
// Test that our compatibility shim can be serialized and deserialized without issues
var settings = SerializationSettings.Default;
var shim = new SerializationConfigurationCompatibilityShim();

// Serialize the shim
var stream = new MemoryStream();
var serializer = new Serializer(new IOStreamStreamWriter(stream, settings, leaveOpen: true), shim);
serializer.Dispose();

// Deserialize the shim
var deserializer = new Deserializer(new PinnedStreamReader(stream, settings), "test");
deserializer.RegisterFactory(typeof(SerializationConfigurationCompatibilityShim), () => new SerializationConfigurationCompatibilityShim());

var deserializedShim = deserializer.ReadObject();
Assert.NotNull(deserializedShim);
Assert.IsType<SerializationConfigurationCompatibilityShim>(deserializedShim);
}
}
}