Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<OutputType>Library</OutputType>
<RootNamespace>Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator</RootNamespace>
<AssemblyName>Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ResultProvider.UnitTests</AssemblyName>
<TargetFramework>net472</TargetFramework>
<TargetFrameworks>net472;$(NetRoslyn)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup Label="Project References">
Expand All @@ -17,10 +17,25 @@
<ProjectReference Include="..\..\..\Core\Test\ResultProvider\Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj" />
<ProjectReference Include="..\..\..\..\Test\PdbUtilities\Roslyn.Test.PdbUtilities.csproj" />
</ItemGroup>
<ItemGroup>
<!--
ResultProvider tests rely on dynamically instantiating objects into the test process via reflection.
As some types cannot exist in .NET Framework (e.g. an [InlineArray] struct) we have to split our tests based on the
target framework of the test project.
-->
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Xml" />
<!--Don't compile NetCore tests-->
<Compile Remove="NetCoreTests\**\*.cs"/>
<!--Keep the folder visible in the IDE-->
<None Include="NetCoreTests\**\*.cs"/>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(NetRoslyn)'">
<!--Only compile the tests under NetCoreTests-->
<Compile Remove="**\*.cs" />
<Compile Include="CSharpResultProviderTestBase.cs"/>
<Compile Include="NetCoreTests\**\*.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\..\Compilers\CSharp\Portable\SymbolDisplay\ObjectDisplay.cs">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests.NetCoreTests;

public class InlineArrayExpansionTests : CSharpResultProviderTestBase
{
[Fact]
public void InlineArrayExpansion()
{
var hostObject = new SampleInlineArray<int>();
for (int i = 0; i < SampleInlineArray<int>.Length; i++)
{
hostObject[i] = i;
}

var value = CreateDkmClrValue(hostObject, typeof(SampleInlineArray<int>), evalFlags: DkmEvaluationResultFlags.None);

const string rootExpr = "new SampleInlineArray<int>()";
var evalResult = (DkmSuccessEvaluationResult)FormatResult(rootExpr, value);
Verify(evalResult,
EvalResult(rootExpr, "0,1,2,3", "Microsoft.CodeAnalysis.ExpressionEvaluator.SampleInlineArray<int>", rootExpr, DkmEvaluationResultFlags.Expandable));

Verify(GetChildren(evalResult),
EvalResult("[0]", "0", "int", "(new SampleInlineArray<int>())[0]", DkmEvaluationResultFlags.None),
EvalResult("[1]", "1", "int", "(new SampleInlineArray<int>())[1]", DkmEvaluationResultFlags.None),
EvalResult("[2]", "2", "int", "(new SampleInlineArray<int>())[2]", DkmEvaluationResultFlags.None),
EvalResult("[3]", "3", "int", "(new SampleInlineArray<int>())[3]", DkmEvaluationResultFlags.None));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal static class RegistryHelpers
var hKeyCurrentUserField = registryType.GetTypeInfo().GetDeclaredField("CurrentUser");
if (hKeyCurrentUserField != null && hKeyCurrentUserField.IsStatic)
{
using var currentUserKey = (IDisposable)hKeyCurrentUserField.GetValue(null);
using var currentUserKey = (IDisposable)hKeyCurrentUserField.GetValue(null)!;
var openSubKeyMethod = currentUserKey.GetType().GetTypeInfo().GetDeclaredMethod("OpenSubKey", [typeof(string), typeof(bool)]);

using var eeKey = (IDisposable?)openSubKeyMethod?.Invoke(currentUserKey, new object[] { RegistryKey, /*writable*/ false });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using BindingFlags = Microsoft.VisualStudio.Debugger.Metadata.BindingFlags;
using FieldInfo = Microsoft.VisualStudio.Debugger.Metadata.FieldInfo;
using CustomAttributeData = Microsoft.VisualStudio.Debugger.Metadata.CustomAttributeData;
using Type = Microsoft.VisualStudio.Debugger.Metadata.Type;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.CodeAnalysis.ExpressionEvaluator;

internal static class InlineArrayHelpers
{
private const string InlineArrayAttributeName = "System.Runtime.CompilerServices.InlineArrayAttribute";

public static bool IsInlineArray(Type t)
{
if (t.IsValueType)
{
IList<CustomAttributeData> attributes = t.GetCustomAttributesData();
foreach (var attribute in attributes)
{
if (attribute.Constructor?.DeclaringType?.FullName?.Equals(InlineArrayAttributeName, StringComparison.Ordinal) == true)
{
return true;
}
}
}

return false;
}

public static bool TryGetInlineArrayInfo(Type t, out int arrayLength, [NotNullWhen(true)] out Type? tElementType)
{
arrayLength = -1;
tElementType = null;

IList<CustomAttributeData> customAttributes = t.GetCustomAttributesData();
foreach (var attribute in customAttributes)
{
if (attribute.Constructor?.DeclaringType?.FullName?.Equals(InlineArrayAttributeName, StringComparison.Ordinal) == true)
{
if (attribute.ConstructorArguments.Count == 1 && attribute.ConstructorArguments[0].Value is int length)
{
arrayLength = length;
}
}
}

// Inline arrays must have length > 0
if (arrayLength <= 0)
{
return false;
}

FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (fields.Length == 1)
{
tElementType = fields[0].FieldType;
}
else
{
// Inline arrays must have exactly one field
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,16 @@ internal Expansion GetTypeExpansion(
return TupleExpansion.CreateExpansion(inspectionContext, declaredTypeAndInfo, value, cardinality);
}

if (InlineArrayHelpers.IsInlineArray(runtimeType) &&
InlineArrayHelpers.TryGetInlineArrayInfo(runtimeType, out int inlineArrayLength, out Type inlineArrayElementType))
{
// Inline arrays are always 1D, zero-based arrays.
return ArrayExpansion.CreateExpansion(
elementTypeAndInfo: new TypeAndCustomInfo(DkmClrType.Create(declaredTypeAndInfo.ClrType.AppDomain, inlineArrayElementType), null),
sizes: new ReadOnlyCollection<int>([inlineArrayLength]),
lowerBounds: new ReadOnlyCollection<int>([0]));
}

return MemberExpansion.CreateExpansion(inspectionContext, declaredTypeAndInfo, value, flags, TypeHelpers.IsVisibleMember, this, isProxyType: false, supportsFavorites);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Formatter.TypeNames.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Formatter.Values.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\FavoritesDataItem.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\InlineArrayHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\TypeAndCustomInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\TypeWalker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\AttributeHelpers.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,9 +565,45 @@ public DkmClrValue GetArrayElement(int[] indices, DkmInspectionContext inspectio
throw new ArgumentNullException(nameof(inspectionContext));
}

var array = (System.Array)RawValue;
var element = array.GetValue(indices);
var type = DkmClrType.Create(this.Type.AppDomain, (TypeImpl)((element == null) ? array.GetType().GetElementType() : element.GetType()));
object element;
System.Type elementType;
if (RawValue is Array array)
{
element = array.GetValue(indices);
elementType = (element == null) ? array.GetType().GetElementType() : element.GetType();
}
else
{
#if NET8_0_OR_GREATER
// Might be an inline array struct
if (indices.Length == 1 && InlineArrayHelpers.IsInlineArray(Type.GetLmrType()))
{
// Since reflection is inadequate to dynamically access inline array elements,
// we have to assume it's the special SampleInlineArray type we define for testing and
// cast appropriately.
element = RawValue switch
{
SampleInlineArray<int> intInlineArray => intInlineArray[indices[0]],
// Add more cases here for other types as needed for testing
_ => throw new InvalidOperationException($"Missing cast case for SampleInlineArray"),
};

var fields = RawValue.GetType().GetFields(System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.DeclaredOnly);
elementType = fields[0].FieldType;
}
else
{
throw new InvalidOperationException("Not an array");
}
#else
throw new InvalidOperationException("Not an array");
#endif
}

var type = DkmClrType.Create(this.Type.AppDomain, (TypeImpl)(elementType));
return new DkmClrValue(
element,
element,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<RootNamespace>Microsoft.CodeAnalysis.ExpressionEvaluator</RootNamespace>
<AssemblyName>Microsoft.CodeAnalysis.ExpressionEvaluator.ResultProvider.Utilities</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net472</TargetFramework>
<TargetFrameworks>net472;$(NetRoslyn)</TargetFrameworks>
<IsShipping>false</IsShipping>
<IsTestUtilityProject>true</IsTestUtilityProject>

Expand All @@ -23,14 +23,14 @@
<!-- Disable CA1825 (Avoid unnecessary zero-length array allocations. Use Array.Empty<X>() instead) as Array.Empty not available in one of the targets for this shared project -->
<NoWarn>$(NoWarn);CA1825</NoWarn>
</PropertyGroup>
<ItemGroup Label="File References">
<ItemGroup Condition="'$(TargetFramework)' == 'net472'" Label="File References">
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\..\..\..\Compilers\Test\Core\Microsoft.CodeAnalysis.Test.Utilities.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<PackageReference Include="System.Collections.Immutable" />
<PackageReference Include="Microsoft.VisualStudio.Debugger.Metadata-implementation" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;

namespace Microsoft.CodeAnalysis.ExpressionEvaluator;

#if NET8_0_OR_GREATER

/// <summary>
/// Reflection APIs are inadequate to introspect inline arrays as they box the value type, making
/// it impossible to take a ref to the first element field and read elements out of it.
/// This definition is intended to be used when testing inline arrays.
/// </summary>
/// <remarks>
/// To support testing new <typeparamref name="T"/> types,
/// add an appropriate cast in <see cref="DkmClrValue.GetArrayElement(int[], DkmInspectionContext)"/>
/// </remarks>
[InlineArray(Length)]
[DebuggerDisplay("{ToString(),nq}")]
internal struct SampleInlineArray<T>
{
public const int Length = 4;
public T _elem0;

public override string ToString()
{
return string.Join(",", MemoryMarshal.CreateSpan(ref _elem0, Length).ToArray());
}
}

#endif
Loading