Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/native/managed/cdacreader/tests/CodeVersionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Moq;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
namespace Microsoft.Diagnostics.DataContractReader.Tests;

using MockCodeVersions = MockDescriptors.CodeVersions;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

internal class ContractDescriptorBuilder : MockMemorySpace.Builder

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was all copied out of MockMemorySpace.cs

{
// These addresses are arbitrary and are used to store the contract descriptor components.
// They should not overlap with any other heap fragment addresses.
private const ulong ContractDescriptorAddr = 0xaaaaaaaa;
private const uint JsonDescriptorAddr = 0xdddddddd;
private const uint ContractPointerDataAddr = 0xeeeeeeee;

private bool _created = false;

private IReadOnlyCollection<string> _contracts;
private IDictionary<DataType, Target.TypeInfo> _types;
private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals;
private IReadOnlyCollection<ulong> _indirectValues;

public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers)
: base(targetTestHelpers)
{ }

public ContractDescriptorBuilder SetContracts(IReadOnlyCollection<string> contracts)
{
if (_created)
throw new InvalidOperationException("Context already created");
_contracts = contracts;
return this;
}

public ContractDescriptorBuilder SetTypes(IDictionary<DataType, Target.TypeInfo> types)
{
if (_created)
throw new InvalidOperationException("Context already created");
_types = types;
return this;
}

public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals)
{
if (_created)
throw new InvalidOperationException("Context already created");
if (_globals != null)
throw new InvalidOperationException("Globals already set");
_globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.TypeName)).ToArray();
_indirectValues = null;
return this;
}

public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection<ulong> indirectValues)
{
if (_created)
throw new InvalidOperationException("Context already created");
if (_globals != null)
throw new InvalidOperationException("Globals already set");
_globals = globals;
_indirectValues = indirectValues;
return this;
}

private MockMemorySpace.HeapFragment CreateContractDescriptor(int jsonLength, int pointerDataCount)
{
byte[] descriptor = new byte[ContractDescriptorHelpers.Size(TargetTestHelpers.Arch.Is64Bit)];
ContractDescriptorHelpers.Fill(descriptor, TargetTestHelpers.Arch, jsonLength, JsonDescriptorAddr, pointerDataCount, ContractPointerDataAddr);
return new MockMemorySpace.HeapFragment
{
Address = ContractDescriptorAddr,
Data = descriptor,
Name = "ContractDescriptor"
};
}

private string MakeContractsJson()
{
if (_contracts.Count == 0)
return string.Empty;
StringBuilder sb = new();
foreach (var c in _contracts)
{
sb.Append($"\"{c}\": 1,");
}
Debug.Assert(sb.Length > 0);
sb.Length--; // remove trailing comma
return sb.ToString();
}

private (MockMemorySpace.HeapFragment json, MockMemorySpace.HeapFragment pointerData) CreateDataDescriptor()
{
string metadataTypesJson = _types is not null ? ContractDescriptorHelpers.MakeTypesJson(_types) : string.Empty;
string metadataGlobalsJson = _globals is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_globals) : string.Empty;
string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty;
byte[] jsonBytes = Encoding.UTF8.GetBytes($$"""
{
"version": 0,
"baseline": "empty",
"contracts": { {{interpolatedContracts}} },
"types": { {{metadataTypesJson}} },
"globals": { {{metadataGlobalsJson}} }
}
""");
MockMemorySpace.HeapFragment json = new()
{
Address = JsonDescriptorAddr,
Data = jsonBytes,
Name = "JsonDescriptor"
};

MockMemorySpace.HeapFragment pointerData;
if (_indirectValues != null)
{
int pointerSize = TargetTestHelpers.PointerSize;
byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize];
int offset = 0;
foreach (var value in _indirectValues)
{
TargetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value);
offset += pointerSize;
}
pointerData = new MockMemorySpace.HeapFragment
{
Address = ContractPointerDataAddr,
Data = pointerDataBytes,
Name = "PointerData"
};
}
else
{
pointerData = new MockMemorySpace.HeapFragment
{
Address = ContractPointerDataAddr,
Data = Array.Empty<byte>(),
Name = "PointerData"
};
}
return (json, pointerData);
}

private ulong CreateDescriptorFragments()
{
if (_created)
throw new InvalidOperationException("Context already created");

(var json, var pointerData) = CreateDataDescriptor();
int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / TargetTestHelpers.PointerSize;
MockMemorySpace.HeapFragment descriptor = CreateContractDescriptor(json.Data.Length, pointerDataCount);

AddHeapFragment(descriptor);
AddHeapFragment(json);
if (pointerData.Data.Length > 0)
AddHeapFragment(pointerData);

_created = true;
return descriptor.Address;
}

public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target)
{
if (_created)
throw new InvalidOperationException("Context already created");
ulong contractDescriptorAddress = CreateDescriptorFragments();
MockMemorySpace.ReadContext context = GetReadContext();
return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

internal unsafe class ContractDescriptorHelpers

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was all copied out of TargetTestHelpers.cs

{
public static int Size(bool is64Bit) => is64Bit ? sizeof(ContractDescriptor64) : sizeof(ContractDescriptor32);

public static void Fill(Span<byte> dest, MockTarget.Architecture arch, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
{
if (arch.Is64Bit)
{
ContractDescriptor64.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr);
}
else
{
ContractDescriptor32.Fill(dest, arch.IsLittleEndian, jsonDescriptorSize, jsonDescriptorAddr, pointerDataCount, pointerDataAddr);
}
}

private struct ContractDescriptor32
{
public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8);
public uint Flags = 0x2 /*32-bit*/ | 0x1;
public uint DescriptorSize;
public uint Descriptor;
public uint PointerDataCount;
public uint Pad0 = 0;
public uint PointerData;

public ContractDescriptor32() { }

public static void Fill(Span<byte> dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
{
ContractDescriptor32 descriptor = new()
{
DescriptorSize = (uint)jsonDescriptorSize,
Descriptor = jsonDescriptorAddr,
PointerDataCount = (uint)pointerDataCount,
PointerData = pointerDataAddr,
};
if (BitConverter.IsLittleEndian != isLittleEndian)
descriptor.ReverseEndianness();

MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest);
}

private void ReverseEndianness()
{
Magic = BinaryPrimitives.ReverseEndianness(Magic);
Flags = BinaryPrimitives.ReverseEndianness(Flags);
DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize);
Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor);
PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount);
Pad0 = BinaryPrimitives.ReverseEndianness(Pad0);
PointerData = BinaryPrimitives.ReverseEndianness(PointerData);
}
}

private struct ContractDescriptor64
{
public ulong Magic = BitConverter.ToUInt64("DNCCDAC\0"u8);
public uint Flags = 0x1;
public uint DescriptorSize;
public ulong Descriptor;
public uint PointerDataCount;
public uint Pad0 = 0;
public ulong PointerData;

public ContractDescriptor64() { }

public static void Fill(Span<byte> dest, bool isLittleEndian, int jsonDescriptorSize, uint jsonDescriptorAddr, int pointerDataCount, uint pointerDataAddr)
{
ContractDescriptor64 descriptor = new()
{
DescriptorSize = (uint)jsonDescriptorSize,
Descriptor = jsonDescriptorAddr,
PointerDataCount = (uint)pointerDataCount,
PointerData = pointerDataAddr,
};
if (BitConverter.IsLittleEndian != isLittleEndian)
descriptor.ReverseEndianness();

MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref descriptor, 1)).CopyTo(dest);
}

private void ReverseEndianness()
{
Magic = BinaryPrimitives.ReverseEndianness(Magic);
Flags = BinaryPrimitives.ReverseEndianness(Flags);
DescriptorSize = BinaryPrimitives.ReverseEndianness(DescriptorSize);
Descriptor = BinaryPrimitives.ReverseEndianness(Descriptor);
PointerDataCount = BinaryPrimitives.ReverseEndianness(PointerDataCount);
Pad0 = BinaryPrimitives.ReverseEndianness(Pad0);
PointerData = BinaryPrimitives.ReverseEndianness(PointerData);
}
}

#region JSON formatting
private static string GetTypeJson(string name, Target.TypeInfo info)
{
string ret = string.Empty;
List<string> fields = info.Size is null ? [] : [$"\"!\":{info.Size}"];
fields.AddRange(info.Fields.Select(f => $"\"{f.Key}\":{(f.Value.TypeName is null ? f.Value.Offset : $"[{f.Value.Offset},\"{f.Value.TypeName}\"]")}"));
return $"\"{name}\":{{{string.Join(',', fields)}}}";
}

public static string MakeTypesJson(IDictionary<DataType, Target.TypeInfo> types)
{
return string.Join(',', types.Select(t => GetTypeJson(t.Key.ToString(), t.Value)));
}

public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals)
{
return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type)));
}

public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals)
{
return string.Join(',', globals.Select(FormatGlobal));

static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global)
{
if (global.Value is ulong value)
{
return $"\"{global.Name}\": {FormatValue(value, global.Type)}";
}
else if (global.IndirectIndex is uint index)
{
return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}";
}
else
{
throw new InvalidOperationException("Global must have a value or indirect index");
}

}
static string FormatValue(ulong value, string? type)
{
return type is null ? $"{value}" : $"[{value},\"{type}\"]";
}
static string FormatIndirect(uint value, string? type)
{
return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]";
}
}

#endregion JSON formatting
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
using System.Text.Json;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.UnitTests;
namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor;

public class ContractDescriptorParserTests
public class ParserTests
{
[Fact]
public void ParsesEmptyContract()
Expand Down
Loading