mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.7 KiB
152 lines
5.7 KiB
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System; |
|
using System.Linq; |
|
using System.Reflection; |
|
|
|
using AwesomeAssertions; |
|
|
|
using ILSpy.Metadata.Filters; |
|
|
|
using NUnit.Framework; |
|
|
|
namespace ICSharpCode.ILSpy.Tests.Metadata; |
|
|
|
[TestFixture] |
|
public class FlagsSchemaInfererTests |
|
{ |
|
[Flags] |
|
enum AllIndependent { None = 0, A = 1, B = 2, C = 4, D = 8 } |
|
|
|
[Flags] |
|
enum SingleMutex |
|
{ |
|
ColourMask = 0x03, |
|
Black = 0x00, |
|
Red = 0x01, |
|
Green = 0x02, |
|
Blue = 0x03, |
|
Sparkly = 0x10, |
|
} |
|
|
|
[Test] |
|
public void Pure_Independent_Enum_Has_No_Mutex_Groups() |
|
{ |
|
var schema = FlagsSchemaInferer.For(typeof(AllIndependent)); |
|
|
|
schema.MutexGroups.Should().BeEmpty(); |
|
schema.IndependentFlags.Select(f => (f.Name, f.Bit)).Should().Equal( |
|
("A", 1u), ("B", 2u), ("C", 4u), ("D", 8u)); |
|
} |
|
|
|
[Test] |
|
public void Mask_Backed_Bits_Land_In_Mutex_Group_And_Other_Bits_Stay_Independent() |
|
{ |
|
var schema = FlagsSchemaInferer.For(typeof(SingleMutex)); |
|
|
|
schema.MutexGroups.Should().HaveCount(1); |
|
var colour = schema.MutexGroups.Single(); |
|
colour.Name.Should().Be("Colour"); |
|
colour.Mask.Should().Be(0x03u); |
|
colour.Values.Select(v => (v.Label, v.Value)).Should().Equal( |
|
("(none)", 0u), |
|
("Red (0001)", 0x01u), |
|
("Green (0002)", 0x02u), |
|
("Blue (0003)", 0x03u)); |
|
|
|
schema.IndependentFlags.Select(f => (f.Name, f.Bit)).Should().Equal( |
|
("Sparkly", 0x10u)); |
|
} |
|
|
|
[Test] |
|
public void TypeAttributes_Surfaces_All_Four_Conventional_Mutex_Groups() |
|
{ |
|
// Snapshot the canonical .NET enum the metadata grid filters most often. The |
|
// inferer must walk the *Mask fields and partition every non-zero value field |
|
// into the first containing mask; bits that don't fit any mask are independent. |
|
var schema = FlagsSchemaInferer.For(typeof(TypeAttributes)); |
|
|
|
var groupNames = schema.MutexGroups.Select(g => g.Name).ToList(); |
|
groupNames.Should().Contain(new[] { "Visibility", "Layout", "ClassSemantics", "StringFormat" }); |
|
|
|
var visibility = schema.MutexGroups.Single(g => g.Name == "Visibility"); |
|
visibility.Mask.Should().Be(0x07u); |
|
visibility.Values.Select(v => v.Value).Should().BeEquivalentTo( |
|
new uint[] { 0, 1, 2, 3, 4, 5, 6, 7 }); |
|
|
|
var layout = schema.MutexGroups.Single(g => g.Name == "Layout"); |
|
layout.Mask.Should().Be(0x18u); |
|
// AutoLayout (0), SequentialLayout (0x08), ExplicitLayout (0x10), and -- added in .NET 11 -- |
|
// ExtendedLayout (0x18, both layout bits set). The inferer surfaces whatever the live enum |
|
// declares inside the mask, so the snapshot tracks the host runtime's TypeAttributes. |
|
layout.Values.Select(v => v.Value).Should().BeEquivalentTo( |
|
new uint[] { 0, 0x08, 0x10, 0x18 }); |
|
|
|
// Independent flags: Abstract, Sealed, SpecialName, Import, Serializable, etc. |
|
var independentNames = schema.IndependentFlags.Select(f => f.Name).ToList(); |
|
independentNames.Should().Contain(new[] { |
|
nameof(TypeAttributes.Abstract), |
|
nameof(TypeAttributes.Sealed), |
|
nameof(TypeAttributes.SpecialName), |
|
nameof(TypeAttributes.Import), |
|
}); |
|
|
|
// And no mask fields leak into either bucket as values. |
|
schema.IndependentFlags.Select(f => f.Name).Should().NotContain("VisibilityMask"); |
|
schema.IndependentFlags.Select(f => f.Name).Should().NotContain("LayoutMask"); |
|
} |
|
|
|
[Test] |
|
public void MethodAttributes_Has_Member_Access_Mutex_Plus_Independent_Modifiers() |
|
{ |
|
var schema = FlagsSchemaInferer.For(typeof(MethodAttributes)); |
|
|
|
var memberAccess = schema.MutexGroups.Single(g => g.Name == "MemberAccess"); |
|
memberAccess.Mask.Should().Be(0x0007u); |
|
memberAccess.Values.Select(v => v.Value).Should().BeEquivalentTo( |
|
new uint[] { 0, 1, 2, 3, 4, 5, 6 }); |
|
|
|
schema.IndependentFlags.Select(f => f.Name).Should().Contain(new[] { |
|
nameof(MethodAttributes.Static), |
|
nameof(MethodAttributes.Final), |
|
nameof(MethodAttributes.Virtual), |
|
nameof(MethodAttributes.Abstract), |
|
}); |
|
} |
|
|
|
[Test] |
|
public void Schema_Is_Cached_So_Repeat_Lookups_Hit_The_Same_Instance() |
|
{ |
|
// Reflection cost is paid once per enum for the lifetime of the process. A |
|
// second call must return the exact same FlagsSchema, not a fresh one. |
|
FlagsSchemaInferer.For(typeof(TypeAttributes)) |
|
.Should().BeSameAs(FlagsSchemaInferer.For(typeof(TypeAttributes))); |
|
} |
|
|
|
[Test] |
|
public void Aliased_Enum_Members_Sharing_A_Value_Render_Once() |
|
{ |
|
// MethodAttributes.PrivateScope and Privatescope (or any duplicate-value field |
|
// pair) must not produce two checkboxes for the same bit. The inferer dedupes |
|
// by value; the first declaration wins. |
|
var schema = FlagsSchemaInferer.For(typeof(MethodAttributes)); |
|
var memberAccess = schema.MutexGroups.Single(g => g.Name == "MemberAccess"); |
|
memberAccess.Values.Select(v => v.Value).Should().OnlyHaveUniqueItems(); |
|
} |
|
}
|
|
|