Skip to content

Commit d8ceca2

Browse files
authored
Improvements to method overrides (SamboyCoding#436)
* Improvements to method overrides * Add `BaseMethod` property * Improve lookup for overrides * Add some barebones testing * Revision * Add more tests * Remove break statement
1 parent 0fc947d commit d8ceca2

File tree

8 files changed

+109
-49
lines changed

8 files changed

+109
-49
lines changed

Cpp2IL.Core.Tests/AccessibilityExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class AccessibilityExtensionsTests
99
[Test]
1010
public void AccessibilityTests()
1111
{
12-
var appContext = GameLoader.LoadSimpleGame();
12+
var appContext = TestGameLoader.LoadSimple2019Game();
1313
var mscorlib = appContext.AssembliesByName["mscorlib"];
1414
var coreModule = appContext.AssembliesByName["UnityEngine.CoreModule"];
1515

Cpp2IL.Core.Tests/DllOutputTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class DllOutputTests
1515
[Test]
1616
public void AllAssembliesBuild()
1717
{
18-
var appContext = GameLoader.LoadSimpleGame();
18+
var appContext = TestGameLoader.LoadSimple2019Game();
1919

2020
var assemblies = new AsmResolverDllOutputFormatDefault().BuildAssemblies(appContext);
2121

@@ -35,7 +35,7 @@ public void AllAssembliesBuild()
3535
[Test]
3636
public void MscorlibIsItsOwnCorLib()
3737
{
38-
var appContext = GameLoader.LoadSimpleGame();
38+
var appContext = TestGameLoader.LoadSimple2019Game();
3939

4040
var assemblies = new AsmResolverDllOutputFormatEmpty().BuildAssemblies(appContext);
4141

@@ -47,7 +47,7 @@ public void MscorlibIsItsOwnCorLib()
4747
[Test]
4848
public void MscorlibHasNoAssemblyReferences()
4949
{
50-
var appContext = GameLoader.LoadSimpleGame();
50+
var appContext = TestGameLoader.LoadSimple2019Game();
5151

5252
var assemblies = new AsmResolverDllOutputFormatEmpty().BuildAssemblies(appContext);
5353

@@ -59,7 +59,7 @@ public void MscorlibHasNoAssemblyReferences()
5959
[Test]
6060
public void MscorlibHasNoTypeReferences()
6161
{
62-
var appContext = GameLoader.LoadSimpleGame();
62+
var appContext = TestGameLoader.LoadSimple2019Game();
6363

6464
var assemblies = new AsmResolverDllOutputFormatEmpty().BuildAssemblies(appContext);
6565

Cpp2IL.Core.Tests/GameLoader.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Linq;
2+
using Cpp2IL.Core.Model.Contexts;
3+
4+
namespace Cpp2IL.Core.Tests;
5+
6+
public class MethodOverridesTests
7+
{
8+
[Test]
9+
public void OverridesTests()
10+
{
11+
var appContext = TestGameLoader.LoadSimple2019Game();
12+
var mscorlib = appContext.AssembliesByName["mscorlib"];
13+
14+
var @enum = mscorlib.GetTypeByFullName("System.Enum")!;
15+
var list = mscorlib.GetTypeByFullName("System.Collections.Generic.List`1")!;
16+
var iList = mscorlib.GetTypeByFullName("System.Collections.IList")!;
17+
var ordinalComparer = mscorlib.GetTypeByFullName("System.OrdinalComparer")!;
18+
using (Assert.EnterMultipleScope())
19+
{
20+
// Simple override
21+
Assert.That(@enum.GetMethod("ToString", 0).BaseMethod, Is.Not.Null);
22+
Assert.That(@enum.GetMethod("ToString", 0).Overrides.Count(), Is.EqualTo(1));
23+
24+
// Simple interface implementation
25+
Assert.That(list.GetMethod("get_Count").BaseMethod, Is.Null);
26+
Assert.That(list.GetMethod("get_Count").Overrides.Count(), Is.EqualTo(3)); // ICollection, ICollection<T>, IReadOnlyCollection<T>
27+
28+
// Explicit interface implementation
29+
Assert.That(list.GetMethod("System.Collections.Generic.ICollection<T>.get_IsReadOnly").Overrides.Count(), Is.EqualTo(1));
30+
31+
// No override
32+
Assert.That(list.GetMethod("EnsureCapacity").Overrides.Count(), Is.EqualTo(0));
33+
34+
// Check that the base method can be found even if higher up in the inheritance chain.
35+
// OrdinalComparer inherits from StringComparer, but StringComparer doesn't override GetHashCode.
36+
Assert.That(ordinalComparer.GetMethod("GetHashCode", 0).BaseMethod?.DeclaringType?.FullName, Is.EqualTo("System.Object"));
37+
38+
// Interface methods should not override anything
39+
Assert.That(iList.Methods.Select(m => m.Overrides.Count()), Is.All.EqualTo(0));
40+
}
41+
}
42+
}

Cpp2IL.Core.Tests/PrimitiveTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class PrimitiveTests
1111
[Test]
1212
public void PrimitiveTypesAreCorLibTypeSignature()
1313
{
14-
var appContext = GameLoader.LoadSimpleGame();
14+
var appContext = TestGameLoader.LoadSimple2019Game();
1515

1616
var assemblies = new AsmResolverDllOutputFormatEmpty().BuildAssemblies(appContext);
1717

Cpp2IL.Core.Tests/TestGameLoader.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Diagnostics.CodeAnalysis;
21
using AssetRipper.Primitives;
2+
using Cpp2IL.Core.Model.Contexts;
33

44
namespace Cpp2IL.Core.Tests;
55

@@ -15,15 +15,17 @@ private static void EnsureInit()
1515
_initialized = true;
1616
}
1717

18-
public static void LoadSimple2019Game()
18+
public static ApplicationAnalysisContext LoadSimple2019Game()
1919
{
2020
EnsureInit();
2121
Cpp2IlApi.InitializeLibCpp2Il(Paths.Simple2019Game.GameAssembly, Paths.Simple2019Game.Metadata, new UnityVersion(2019, 4, 34, UnityVersionType.Final, 1));
22+
return Cpp2IlApi.CurrentAppContext!;
2223
}
2324

24-
public static void LoadSimple2022Game()
25+
public static ApplicationAnalysisContext LoadSimple2022Game()
2526
{
2627
EnsureInit();
2728
Cpp2IlApi.InitializeLibCpp2Il(Paths.Simple2022Game.GameAssembly, Paths.Simple2022Game.Metadata, new UnityVersion(2022, 3, 35, UnityVersionType.Final, 1));
29+
return Cpp2IlApi.CurrentAppContext!;
2830
}
2931
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Linq;
2+
using Cpp2IL.Core.Model.Contexts;
3+
4+
namespace Cpp2IL.Core.Tests;
5+
6+
internal static class TypeAnalysisContextExtensions
7+
{
8+
public static MethodAnalysisContext GetMethod(this TypeAnalysisContext type, string methodName)
9+
{
10+
return type.Methods.Single(m => m.Name == methodName);
11+
}
12+
13+
public static MethodAnalysisContext GetMethod(this TypeAnalysisContext type, string methodName, int parameterCount)
14+
{
15+
return type.Methods.Single(m => m.Name == methodName && m.ParameterCount == parameterCount);
16+
}
17+
}

Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public class MethodAnalysisContext : HasCustomAttributesAndName, IMethodInfoProv
8888

8989
protected Memory<byte>? rawMethodBody;
9090

91+
public MethodAnalysisContext? BaseMethod => Overrides.FirstOrDefault(m => m.DeclaringType?.IsInterface is false);
92+
9193
/// <summary>
9294
/// The set of methods which this method overrides.
9395
/// </summary>
@@ -108,22 +110,29 @@ public virtual IEnumerable<MethodAnalysisContext> Overrides
108110

109111
return GetOverriddenMethods(declaringTypeDefinition, vtable);
110112

111-
static void GetParentTypeAndSlot(Il2CppTypeDefinition declaringTypeDefinition, int vtableIndex, out Il2CppTypeReflectionData? parentType, out int slot)
113+
bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method)
112114
{
113-
var interfaceOffsets = declaringTypeDefinition.InterfaceOffsets;
114-
for (var i = interfaceOffsets.Length - 1; i >= 0; i--)
115+
if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
116+
{
117+
var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
118+
if (genericMethod is not null)
119+
{
120+
method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments.ToArray(), []);
121+
return true;
122+
}
123+
}
124+
else
115125
{
116-
var interfaceOffset = interfaceOffsets[i];
117-
if (vtableIndex >= interfaceOffset.offset)
126+
var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot);
127+
if (baseMethod is not null)
118128
{
119-
slot = vtableIndex - interfaceOffsets[i].offset;
120-
parentType = interfaceOffset.Type;
121-
return;
129+
method = baseMethod;
130+
return true;
122131
}
123132
}
124133

125-
parentType = declaringTypeDefinition.BaseType;
126-
slot = vtableIndex;
134+
method = null;
135+
return false;
127136
}
128137

129138
IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
@@ -137,23 +146,29 @@ IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition dec
137146
if (vtableEntry.AsMethod() != Definition)
138147
continue;
139148

140-
GetParentTypeAndSlot(declaringTypeDefinition, i, out var parentType, out var slot);
141-
142-
var parentTypeContext = parentType?.ToContext(CustomAttributeAssembly);
143-
if (parentTypeContext == null)
144-
continue;
145-
146-
if (parentTypeContext is GenericInstanceTypeAnalysisContext genericInstanceType)
149+
// Normal inheritance
150+
var baseType = DeclaringType?.BaseType;
151+
while (baseType is not null)
147152
{
148-
var parentMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
149-
if (parentMethod is not null)
150-
yield return new ConcreteGenericMethodAnalysisContext(parentMethod, genericInstanceType.GenericArguments.ToArray(), []);
153+
if (TryGetMethodForSlot(baseType, i, out var method))
154+
{
155+
yield return method;
156+
break; // We only want direct overrides, not the entire inheritance chain.
157+
}
158+
baseType = baseType.BaseType;
151159
}
152-
else
160+
161+
// Interface inheritance
162+
foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
153163
{
154-
var parentMethod = parentTypeContext.Methods.FirstOrDefault(m => m.Slot == slot);
155-
if (parentMethod is not null)
156-
yield return parentMethod;
164+
if (i >= interfaceOffset.offset)
165+
{
166+
var interfaceTypeContext = interfaceOffset.Type.ToContext(CustomAttributeAssembly);
167+
if (interfaceTypeContext != null && TryGetMethodForSlot(interfaceTypeContext, i - interfaceOffset.offset, out var method))
168+
{
169+
yield return method;
170+
}
171+
}
157172
}
158173
}
159174
}

0 commit comments

Comments
 (0)