Skip to content

Commit 0eaa411

Browse files
committed
feat: Implement delegate placeholder rewriting for context-bound signatures
- replace DelegateWithCtxParamProcessor with DelegatePlaceholderProcessor in the patch pipeline - generate deterministic placeholder delegate types under UnifiedServerProcess.Delegates.CtxDelegateTypes - propagate delegate type rewrites through fields, locals, by-ref paths, call sites, inheritance-related methods, and field-like events - extend MethodSignatureUpdateSession to plan and validate return type changes in addition to parameter changes - register generated delegate Invoke/BeginInvoke methods as context-bound by prepending root context
1 parent d553d02 commit 0eaa411

40 files changed

Lines changed: 2312 additions & 286 deletions
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using ModFramework;
2+
using Mono.Cecil;
3+
using Mono.Cecil.Cil;
4+
using OTAPI.UnifiedServerProcess.Core;
5+
using OTAPI.UnifiedServerProcess.Core.FunctionalFeatures;
6+
using OTAPI.UnifiedServerProcess.Core.Patching.FieldFilterPatching;
7+
using OTAPI.UnifiedServerProcess.Loggers;
8+
using System;
9+
using System.IO;
10+
using Xunit;
11+
12+
namespace OTAPI.UnifiedServerProcess.UnitTests
13+
{
14+
public class DelegatePlaceholderProcessorByRefTests
15+
{
16+
[Fact]
17+
public void Apply_PropagatesDelegateRewriteThroughByRefFieldAndLocalAddresses() {
18+
using var module = CreateModuleWithResolver("USP.DelegatePlaceholderProcessor.ByRef");
19+
20+
var rootContextDef = new TypeDefinition("UnifiedServerProcess", "RootContext",
21+
TypeAttributes.Public | TypeAttributes.Class, module.TypeSystem.Object);
22+
module.Types.Add(rootContextDef);
23+
24+
var hookDel = CreateDelegateType(module, "Terraria.DataStructures", "HookDel",
25+
module.TypeSystem.Void, module.TypeSystem.Int32);
26+
var seedDel = CreateDelegateType(module, "Terraria.WorldBuilding", "SeedChangedDel",
27+
module.TypeSystem.Void, module.TypeSystem.Boolean);
28+
29+
var placementHook = new TypeDefinition("Terraria.DataStructures", "PlacementHook",
30+
TypeAttributes.Public | TypeAttributes.Class, module.TypeSystem.Object);
31+
module.Types.Add(placementHook);
32+
33+
var placementHookCtor = new MethodDefinition(".ctor",
34+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
35+
module.TypeSystem.Void) { HasThis = true };
36+
placementHookCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
37+
placementHook.Methods.Add(placementHookCtor);
38+
39+
var hookField = new FieldDefinition("hook", FieldAttributes.Public, hookDel);
40+
placementHook.Fields.Add(hookField);
41+
42+
var placementHookCtorWithStore = new MethodDefinition(".ctor",
43+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
44+
module.TypeSystem.Void) { HasThis = true };
45+
placementHookCtorWithStore.Parameters.Add(new ParameterDefinition(hookDel));
46+
placementHookCtorWithStore.Parameters.Add(new ParameterDefinition(module.TypeSystem.Int32));
47+
placementHookCtorWithStore.Parameters.Add(new ParameterDefinition(module.TypeSystem.Int32));
48+
placementHookCtorWithStore.Parameters.Add(new ParameterDefinition(module.TypeSystem.Boolean));
49+
{
50+
var il = placementHookCtorWithStore.Body.GetILProcessor();
51+
il.Append(il.Create(OpCodes.Ldarg_0));
52+
il.Append(il.Create(OpCodes.Ldarg_1));
53+
il.Append(il.Create(OpCodes.Stfld, hookField));
54+
il.Append(il.Create(OpCodes.Ret));
55+
}
56+
placementHook.Methods.Add(placementHookCtorWithStore);
57+
58+
var worldGenOption = new TypeDefinition("Terraria.WorldBuilding", "AWorldGenerationOption",
59+
TypeAttributes.Public | TypeAttributes.Class, module.TypeSystem.Object);
60+
module.Types.Add(worldGenOption);
61+
worldGenOption.Fields.Add(new FieldDefinition("OnOptionStateChanged", FieldAttributes.Public | FieldAttributes.Static, seedDel));
62+
63+
var host = new TypeDefinition("Tests", "Host",
64+
TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.Class,
65+
module.TypeSystem.Object);
66+
module.Types.Add(host);
67+
68+
var calleeFieldByRef = new MethodDefinition("CalleeFieldByRef",
69+
MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void);
70+
calleeFieldByRef.Parameters.Add(new ParameterDefinition(new ByReferenceType(hookDel)));
71+
calleeFieldByRef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
72+
host.Methods.Add(calleeFieldByRef);
73+
74+
var calleeLocalByRef = new MethodDefinition("CalleeLocalByRef",
75+
MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void);
76+
calleeLocalByRef.Parameters.Add(new ParameterDefinition(new ByReferenceType(hookDel)));
77+
calleeLocalByRef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
78+
host.Methods.Add(calleeLocalByRef);
79+
80+
var callerFieldByRef = new MethodDefinition("CallerFieldByRef",
81+
MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void) {
82+
Body = { InitLocals = true }
83+
};
84+
callerFieldByRef.Body.Variables.Add(new VariableDefinition(placementHook));
85+
{
86+
var il = callerFieldByRef.Body.GetILProcessor();
87+
il.Append(il.Create(OpCodes.Newobj, placementHookCtor));
88+
il.Append(il.Create(OpCodes.Stloc_0));
89+
il.Append(il.Create(OpCodes.Ldloc_0));
90+
il.Append(il.Create(OpCodes.Ldflda, hookField));
91+
il.Append(il.Create(OpCodes.Call, calleeFieldByRef));
92+
il.Append(il.Create(OpCodes.Ret));
93+
}
94+
host.Methods.Add(callerFieldByRef);
95+
96+
var callerLocalByRef = new MethodDefinition("CallerLocalByRef",
97+
MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void) {
98+
Body = { InitLocals = true }
99+
};
100+
callerLocalByRef.Body.Variables.Add(new VariableDefinition(placementHook));
101+
callerLocalByRef.Body.Variables.Add(new VariableDefinition(hookDel));
102+
{
103+
var il = callerLocalByRef.Body.GetILProcessor();
104+
il.Append(il.Create(OpCodes.Newobj, placementHookCtor));
105+
il.Append(il.Create(OpCodes.Stloc_0));
106+
il.Append(il.Create(OpCodes.Ldloc_0));
107+
il.Append(il.Create(OpCodes.Ldfld, hookField));
108+
il.Append(il.Create(OpCodes.Stloc_1));
109+
il.Append(il.Create(OpCodes.Ldloca_S, callerLocalByRef.Body.Variables[1]));
110+
il.Append(il.Create(OpCodes.Call, calleeLocalByRef));
111+
il.Append(il.Create(OpCodes.Ret));
112+
}
113+
host.Methods.Add(callerLocalByRef);
114+
115+
var analyzers = new AnalyzerGroups(new NullLogger(), module);
116+
var processor = new DelegatePlaceholderProcessor(rootContextDef, analyzers);
117+
var source = new FilterArgumentSource(module, []);
118+
119+
processor.Apply(new TestComponent(new NullLogger()), ref source);
120+
processor.ClearJumpSitesCache();
121+
122+
var rewrittenHookType = hookField.FieldType;
123+
Assert.NotEqual(hookDel.FullName, rewrittenHookType.FullName);
124+
125+
Assert.Equal(rewrittenHookType.FullName, placementHookCtorWithStore.Parameters[0].ParameterType.FullName);
126+
127+
Assert.IsType<ByReferenceType>(calleeFieldByRef.Parameters[0].ParameterType);
128+
Assert.Equal(rewrittenHookType.FullName, ((ByReferenceType)calleeFieldByRef.Parameters[0].ParameterType).ElementType.FullName);
129+
130+
Assert.IsType<ByReferenceType>(calleeLocalByRef.Parameters[0].ParameterType);
131+
Assert.Equal(rewrittenHookType.FullName, ((ByReferenceType)calleeLocalByRef.Parameters[0].ParameterType).ElementType.FullName);
132+
133+
Assert.Equal(rewrittenHookType.FullName, callerLocalByRef.Body.Variables[1].VariableType.FullName);
134+
}
135+
136+
private static ModuleDefinition CreateModuleWithResolver(string name) {
137+
var resolver = new DefaultAssemblyResolver();
138+
resolver.AddSearchDirectory(AppContext.BaseDirectory);
139+
resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(object).Assembly.Location)!);
140+
resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(DefaultCollection<>).Assembly.Location)!);
141+
142+
var parameters = new ModuleParameters {
143+
Kind = ModuleKind.Dll,
144+
AssemblyResolver = resolver,
145+
};
146+
return ModuleDefinition.CreateModule(name, parameters);
147+
}
148+
149+
private static TypeDefinition CreateDelegateType(
150+
ModuleDefinition module,
151+
string ns,
152+
string name,
153+
TypeReference returnType,
154+
params TypeReference[] parameters) {
155+
156+
var multicastDelegate = module.ImportReference(typeof(MulticastDelegate));
157+
var del = new TypeDefinition(ns, name,
158+
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
159+
multicastDelegate);
160+
module.Types.Add(del);
161+
162+
var ctor = new MethodDefinition(".ctor",
163+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
164+
module.TypeSystem.Void) {
165+
HasThis = true,
166+
ImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed,
167+
};
168+
ctor.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object));
169+
ctor.Parameters.Add(new ParameterDefinition(module.TypeSystem.IntPtr));
170+
del.Methods.Add(ctor);
171+
172+
var invoke = new MethodDefinition("Invoke",
173+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
174+
returnType) {
175+
HasThis = true,
176+
ImplAttributes = MethodImplAttributes.Runtime | MethodImplAttributes.Managed,
177+
};
178+
foreach (var p in parameters) {
179+
invoke.Parameters.Add(new ParameterDefinition(p));
180+
}
181+
del.Methods.Add(invoke);
182+
183+
return del;
184+
}
185+
186+
private sealed class TestComponent(ILogger logger) : LoggedComponent(logger)
187+
{
188+
public override string Name => "unit-test";
189+
}
190+
191+
private sealed class NullLogger : ILogger
192+
{
193+
public void Progress(ILoggedComponent sender, int iteration, int progress, int total, string message, int indent = 0) { }
194+
public void Progress(ILoggedComponent sender, int progress, int total, string message, int indent = 0) { }
195+
public void Progress(ILoggedComponent sender, int iteration, int progress, int total, string message, int indent = 0, params object[] args) { }
196+
public void Progress(ILoggedComponent sender, int progress, int total, string message, int indent = 0, params object[] args) { }
197+
public void Debug(ILoggedComponent sender, int indent, string log, params object[] args) { }
198+
public void Info(ILoggedComponent sender, int indent, string log, params object[] args) { }
199+
public void Warn(ILoggedComponent sender, int indent, string log, params object[] args) { }
200+
public void Error(ILoggedComponent sender, int indent, string log, Exception ex, params object[] args) { }
201+
public void Error(ILoggedComponent sender, int indent, string log, params object[] args) { }
202+
public void Fatal(ILoggedComponent sender, int indent, string log, params object[] args) { }
203+
public void Debug(ILoggedComponent sender, string log, params object[] args) { }
204+
public void Info(ILoggedComponent sender, string log, params object[] args) { }
205+
public void Warn(ILoggedComponent sender, string log, params object[] args) { }
206+
public void Error(ILoggedComponent sender, string log, Exception ex, params object[] args) { }
207+
public void Error(ILoggedComponent sender, string log, params object[] args) { }
208+
public void Fatal(ILoggedComponent sender, string log, params object[] args) { }
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)