[dotnet] preliminary cut for class-redirector (#17951)
This is the preliminary version of class-redirector addressing issue #16671
This commit is contained in:
Родитель
098d9c5b9f
Коммит
bef5d47ef4
|
@ -201,6 +201,15 @@ namespace ObjCRuntime {
|
|||
|
||||
internal static unsafe InitializationOptions* options;
|
||||
|
||||
#if NET
|
||||
public static class ClassHandles
|
||||
{
|
||||
internal static unsafe void InitializeClassHandles (MTClassMap* map)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET
|
||||
[BindingImpl (BindingImplOptions.Optimizable)]
|
||||
internal unsafe static bool IsCoreCLR {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
global using NUnit.Framework;
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
using ClassRedirector;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace ClassRedirectorTests;
|
||||
|
||||
public class XmlTests {
|
||||
[Test]
|
||||
public void WritesCorrectXml ()
|
||||
{
|
||||
var map = new CSToObjCMap () {
|
||||
["Foo"] = new ObjCNameIndex ("Bar", 2),
|
||||
["Baz"] = new ObjCNameIndex ("Zed", 3),
|
||||
};
|
||||
|
||||
var doc = CSToObjCMap.ToXDocument (map);
|
||||
|
||||
var elem = doc.Elements ().Where (el => el.Name == "CSToObjCMap").FirstOrDefault ();
|
||||
Assert.IsNotNull (elem, "no map");
|
||||
Assert.That (elem.Elements ().Count (), Is.EqualTo (2), "Incorrect number of children");
|
||||
|
||||
var cselem = XElementWithAttribute (elem, "CSName", "Foo");
|
||||
Assert.IsNotNull (cselem, "missing Foo elem");
|
||||
var nameElem = NameElem (cselem);
|
||||
Assert.IsNotNull (nameElem, "no name elem1");
|
||||
Assert.That (nameElem.Value, Is.EqualTo ("Bar"));
|
||||
var indexElem = IndexElem (cselem);
|
||||
Assert.IsNotNull (indexElem, "no value elem1");
|
||||
Assert.That ((int) indexElem, Is.EqualTo (2));
|
||||
|
||||
cselem = XElementWithAttribute (elem, "CSName", "Baz");
|
||||
Assert.IsNotNull (cselem, "missing Baz elem");
|
||||
nameElem = NameElem (cselem);
|
||||
Assert.IsNotNull (nameElem, "no name elem2");
|
||||
indexElem = IndexElem (cselem);
|
||||
Assert.That (nameElem.Value, Is.EqualTo ("Zed"));
|
||||
Assert.IsNotNull (indexElem, "no value elem2");
|
||||
Assert.That ((int) indexElem, Is.EqualTo (3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadsCorrectXml ()
|
||||
{
|
||||
var text = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<CSToObjCMap>
|
||||
<Element CSName=""Foo"">
|
||||
<ObjNameIndex>
|
||||
<Name>Bar</Name>
|
||||
<Index>2</Index>
|
||||
</ObjNameIndex>
|
||||
</Element>
|
||||
<Element CSName=""Baz"">
|
||||
<ObjNameIndex>
|
||||
<Name>Zed</Name>
|
||||
<Index>3</Index>
|
||||
</ObjNameIndex>
|
||||
</Element>
|
||||
</CSToObjCMap>";
|
||||
|
||||
using var reader = new StringReader (text);
|
||||
var doc = XDocument.Load (reader);
|
||||
|
||||
var map = CSToObjCMap.FromXDocument (doc);
|
||||
Assert.IsNotNull (map, "no map");
|
||||
Assert.That (map.Count (), Is.EqualTo (2));
|
||||
|
||||
Assert.True (map.TryGetValue ("Foo", out var nameIndex), "no nameIndex");
|
||||
Assert.That (nameIndex.ObjCName, Is.EqualTo ("Bar"), "no bar name");
|
||||
Assert.That (nameIndex.MapIndex, Is.EqualTo (2), "no bar index");
|
||||
|
||||
Assert.True (map.TryGetValue ("Baz", out var nameIndex1));
|
||||
Assert.That (nameIndex1.ObjCName, Is.EqualTo ("Zed"), "no bar name");
|
||||
Assert.That (nameIndex1.MapIndex, Is.EqualTo (3), "no bar index");
|
||||
}
|
||||
|
||||
|
||||
static XElement? XElementWithAttribute (XElement el, string attrName, string attrValue)
|
||||
{
|
||||
return el.Elements ().Where (e => {
|
||||
var attr = e.Attribute (attrName);
|
||||
return attr is not null && attr.Value == attrValue;
|
||||
}).FirstOrDefault ();
|
||||
}
|
||||
|
||||
static XElement? NameElem (XElement el) => FirstElementNamed (el, "Name");
|
||||
|
||||
static XElement? IndexElem (XElement el) => FirstElementNamed (el, "Index");
|
||||
|
||||
static XElement? FirstElementNamed (XElement el, string name)
|
||||
{
|
||||
return el.Descendants ().Where (e => e.Name == name).FirstOrDefault ();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RootNamespace>class_redirector_tests</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\common\CSToObjCMap.cs">
|
||||
<Link>CSToObjCMap.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\common\ObjCNameIndex.cs">
|
||||
<Link>ObjCNameIndex.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\common\StaticRegistrarFile.cs">
|
||||
<Link>StaticRegistrarFile.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 25.0.1705.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "class-redirector", "class-redirector\class-redirector.csproj", "{0723580E-2C56-4460-BDDD-DEF38E0F543F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "class-redirector-tests", "class-redirector-tests\class-redirector-tests.csproj", "{16FD4B5E-5537-46A2-B573-0424A51472AA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {ECD11F77-045E-4B88-BF92-28ED04BBD631}
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
Policies = $0
|
||||
$0.TextStylePolicy = $1
|
||||
$1.FileWidth = 80
|
||||
$1.TabWidth = 8
|
||||
$1.IndentWidth = 8
|
||||
$1.scope = text/x-csharp
|
||||
$0.CSharpFormattingPolicy = $2
|
||||
$2.IndentSwitchSection = False
|
||||
$2.NewLinesForBracesInTypes = False
|
||||
$2.NewLinesForBracesInProperties = False
|
||||
$2.NewLinesForBracesInAccessors = False
|
||||
$2.NewLinesForBracesInAnonymousMethods = False
|
||||
$2.NewLinesForBracesInControlBlocks = False
|
||||
$2.NewLinesForBracesInAnonymousTypes = False
|
||||
$2.NewLinesForBracesInObjectCollectionArrayInitializers = False
|
||||
$2.NewLinesForBracesInLambdaExpressionBody = False
|
||||
$2.NewLineForElse = False
|
||||
$2.NewLineForCatch = False
|
||||
$2.NewLineForFinally = False
|
||||
$2.NewLineForMembersInObjectInit = False
|
||||
$2.NewLineForMembersInAnonymousTypes = False
|
||||
$2.NewLineForClausesInQuery = False
|
||||
$2.SpacingAfterMethodDeclarationName = True
|
||||
$2.SpaceAfterMethodCallName = True
|
||||
$2.SpaceBeforeOpenSquareBracket = True
|
||||
$2.scope = text/x-csharp
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,106 @@
|
|||
using System.IO;
|
||||
using Mono.Options;
|
||||
using System.Xml.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ClassRedirector {
|
||||
public class Program {
|
||||
public static int Main (string [] args)
|
||||
{
|
||||
try {
|
||||
return Main2 (args);
|
||||
} catch (Exception e) {
|
||||
Console.Error.WriteLine (e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main2 (string [] args)
|
||||
{
|
||||
var doHelp = false;
|
||||
string inputDirectory = "";
|
||||
var options = new OptionSet () {
|
||||
{ "h|?|help", o => doHelp = true },
|
||||
{ "i=|input-directory=", d => inputDirectory = d },
|
||||
};
|
||||
|
||||
if (doHelp) {
|
||||
options.WriteOptionDescriptions (Console.Out);
|
||||
Console.WriteLine ($"This program takes an input directory and looks for the file '{StaticRegistrarFile.Name}'.");
|
||||
Console.WriteLine ("Upon finding it, it uses that as a map for finding all C# classes defined in dll's in the");
|
||||
Console.WriteLine ("directory and rewrites the classes' definition and usage of class_ptr to be more");
|
||||
Console.WriteLine ("efficient and, if possible, to no longer need a static constructor.");
|
||||
Console.WriteLine ("This program also requires that the directory contains one of the Microsoft platform dlls.");
|
||||
Console.WriteLine ("Classes in the directory are modified in place.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty (inputDirectory)) {
|
||||
throw new Exception ($"input-directory is required");
|
||||
}
|
||||
|
||||
if (!Directory.Exists (inputDirectory)) {
|
||||
throw new Exception ($"input-directory {inputDirectory} does not exist.");
|
||||
}
|
||||
|
||||
if (!DirectoryIsWritable (inputDirectory)) {
|
||||
throw new Exception ($"input-directory {inputDirectory} is not writable");
|
||||
}
|
||||
|
||||
var registrarFile = Path.Combine (inputDirectory, StaticRegistrarFile.Name);
|
||||
if (!File.Exists (registrarFile)) {
|
||||
throw new Exception ($"map file {registrarFile} does not exist.");
|
||||
}
|
||||
|
||||
var dllsToProcess = CollectDlls (inputDirectory);
|
||||
var xamarinDll = FindXamarinDll (dllsToProcess);
|
||||
|
||||
if (xamarinDll is null)
|
||||
throw new Exception ($"unable to find platform dll in {inputDirectory}");
|
||||
|
||||
var map = ReadRegistrarFile (registrarFile);
|
||||
|
||||
var rewriter = new Rewriter (map, xamarinDll, dllsToProcess);
|
||||
rewriter.Process ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool DirectoryIsWritable (string path)
|
||||
{
|
||||
var info = new DirectoryInfo (path);
|
||||
return !info.Attributes.HasFlag (FileAttributes.ReadOnly);
|
||||
}
|
||||
|
||||
static string [] CollectDlls (string dir)
|
||||
{
|
||||
return Directory.GetFiles (dir, "*.dll"); // GetFiles returns full paths
|
||||
}
|
||||
|
||||
static string [] xamarinDlls = new string [] {
|
||||
"Microsoft.iOS.dll",
|
||||
"Microsoft.macOS.dll",
|
||||
"Microsoft.tvOS.dll",
|
||||
};
|
||||
|
||||
static bool IsXamarinDll (string p)
|
||||
{
|
||||
return xamarinDlls.FirstOrDefault (dll => p.EndsWith (dll, StringComparison.Ordinal)) is not null;
|
||||
}
|
||||
|
||||
static string? FindXamarinDll (string [] paths)
|
||||
{
|
||||
return paths.FirstOrDefault (IsXamarinDll);
|
||||
}
|
||||
|
||||
static CSToObjCMap ReadRegistrarFile (string path)
|
||||
{
|
||||
var doc = XDocument.Load (path);
|
||||
var map = CSToObjCMap.FromXDocument (doc);
|
||||
if (map is null)
|
||||
throw new Exception ($"Unable to read static registrar map file {path}");
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
|
||||
namespace ClassRedirector {
|
||||
public class Rewriter {
|
||||
const string classHandleName = "ObjRuntime.Runtime.ClassHandles";
|
||||
const string mtClassMapName = "ObjCRuntime.Runtime.MTClassMap";
|
||||
const string nativeHandleName = "ObjCRuntime.NativeHandle";
|
||||
const string initClassHandlesName = "InitializeClassHandles";
|
||||
const string classPtrName = "class_ptr";
|
||||
CSToObjCMap map;
|
||||
string pathToXamarinAssembly;
|
||||
string [] assembliesToPatch;
|
||||
SimpleAssemblyResolver resolver;
|
||||
Dictionary<string, FieldDefinition> csTypeToFieldDef = new Dictionary<string, FieldDefinition> ();
|
||||
|
||||
public Rewriter (CSToObjCMap map, string pathToXamarinAssembly, string [] assembliesToPatch)
|
||||
{
|
||||
this.map = map;
|
||||
this.pathToXamarinAssembly = pathToXamarinAssembly;
|
||||
this.assembliesToPatch = assembliesToPatch;
|
||||
resolver = new SimpleAssemblyResolver (assembliesToPatch);
|
||||
}
|
||||
|
||||
public void Process ()
|
||||
{
|
||||
var classMap = CreateClassHandles ();
|
||||
PatchClassPtrUsage (classMap);
|
||||
}
|
||||
|
||||
Dictionary<string, FieldDefinition> CreateClassHandles ()
|
||||
{
|
||||
var classMap = new Dictionary<string, FieldDefinition> ();
|
||||
using var module = ModuleDefinition.ReadModule (pathToXamarinAssembly);
|
||||
|
||||
var classHandles = LocateClassHandles (module);
|
||||
if (classHandles is null)
|
||||
throw new Exception ($"Unable to find {classHandleName} type in {pathToXamarinAssembly}");
|
||||
|
||||
var initMethod = classHandles.Methods.FirstOrDefault (m => m.Name == initClassHandlesName);
|
||||
if (initMethod is null)
|
||||
throw new Exception ($"Unable to find {initClassHandlesName} method in {classHandles.Name}");
|
||||
|
||||
var processor = initMethod.Body.GetILProcessor ();
|
||||
|
||||
var mtClassMapDef = LocateMTClassMap (module);
|
||||
if (mtClassMapDef is null)
|
||||
throw new Exception ($"Unable to find {mtClassMapName} in {pathToXamarinAssembly}");
|
||||
|
||||
var nativeHandle = module.Types.FirstOrDefault (t => t.Name == nativeHandleName);
|
||||
if (nativeHandle is null)
|
||||
throw new Exception ($"Unable to find {nativeHandleName} in {pathToXamarinAssembly}");
|
||||
|
||||
var nativeHandleOpImplicit = FindOpImplicit (nativeHandle);
|
||||
if (nativeHandleOpImplicit is null)
|
||||
throw new Exception ($"Unable to find implicit cast in {nativeHandleName}");
|
||||
|
||||
foreach (var (csName, nameIndex) in map) {
|
||||
var fieldDef = AddPublicStaticField (classHandles, nameIndex.ObjCName, nativeHandle);
|
||||
AddInitializer (nativeHandleOpImplicit, processor, mtClassMapDef, nameIndex.MapIndex, fieldDef);
|
||||
classMap [csName] = fieldDef;
|
||||
}
|
||||
|
||||
module.Write ();
|
||||
return classMap;
|
||||
}
|
||||
|
||||
MethodDefinition? FindOpImplicit (TypeDefinition nativeHandle)
|
||||
{
|
||||
return nativeHandle.Methods.FirstOrDefault (m => m.Name == "op_Implicit" && m.ReturnType == nativeHandle &&
|
||||
m.Parameters.Count == 1 && m.Parameters [0].ParameterType == nativeHandle.Module.TypeSystem.IntPtr);
|
||||
}
|
||||
|
||||
void AddInitializer (MethodReference nativeHandleOpImplicit, ILProcessor il, TypeDefinition mtClassMapDef, int index, FieldDefinition fieldDef)
|
||||
{
|
||||
// Assuming that we have a method that looks like this:
|
||||
// internal static unsafe void InitializeClassHandles (MTClassMap* map)
|
||||
// {
|
||||
// }
|
||||
// We should have a compiled method that looks like this:
|
||||
// nop
|
||||
// ret
|
||||
//
|
||||
// For each handle that we define, we should add the following instructions:
|
||||
// ldarg.0
|
||||
// ldc.i4 index
|
||||
// conv.i
|
||||
// sizeof ObjCRuntime.Runtime.MTClassMap
|
||||
// mul
|
||||
// add
|
||||
// ldfld ObjCRuntime.Runtime.MTClassMap.handle
|
||||
// call ObjCRuntime.NativeHandle ObjCRuntime.NativeHandle::op_Implicit(System.IntPtr)
|
||||
// stsfld fieldDef
|
||||
var handleRef = mtClassMapDef.Fields.First (f => f.Name == "handle");
|
||||
var last = il.Body.Instructions.Last ();
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Ldarg_0));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Ldc_I4, index));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Conv_I));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Sizeof, mtClassMapDef));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Mul));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Add));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Ldfld, handleRef));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Call, nativeHandleOpImplicit));
|
||||
il.InsertBefore (last, Instruction.Create (OpCodes.Stsfld, fieldDef));
|
||||
}
|
||||
|
||||
FieldDefinition AddPublicStaticField (TypeDefinition inType, string fieldName, TypeReference fieldType)
|
||||
{
|
||||
var fieldDef = new FieldDefinition (fieldName, FieldAttributes.Public | FieldAttributes.Static, fieldType);
|
||||
inType.Fields.Add (fieldDef);
|
||||
return fieldDef;
|
||||
}
|
||||
|
||||
TypeDefinition? LocateClassHandles (ModuleDefinition module)
|
||||
{
|
||||
return module.GetType (classHandleName);
|
||||
}
|
||||
|
||||
TypeDefinition? LocateMTClassMap (ModuleDefinition module)
|
||||
{
|
||||
return module.GetType (mtClassMapName);
|
||||
}
|
||||
|
||||
void PatchClassPtrUsage (Dictionary<string, FieldDefinition> classMap)
|
||||
{
|
||||
foreach (var path in assembliesToPatch) {
|
||||
using var module = ModuleDefinition.ReadModule (path);
|
||||
PatchClassPtrUsage (classMap, module);
|
||||
module.Write ();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchClassPtrUsage (Dictionary<string, FieldDefinition> classMap, ModuleDefinition module)
|
||||
{
|
||||
foreach (var cl in module.Types) {
|
||||
if (classMap.TryGetValue (cl.FullName, out var classPtrField)) {
|
||||
PatchClassPtrUsage (cl, classPtrField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PatchClassPtrUsage (TypeDefinition cl, FieldDefinition classPtrField)
|
||||
{
|
||||
var class_ptr = cl.Fields.FirstOrDefault (f => f.Name == classPtrName);
|
||||
if (class_ptr is null) {
|
||||
throw new Exception ($"Error processing class {cl.FullName} - no {classPtrName} field.");
|
||||
}
|
||||
|
||||
// step 1: remove the field
|
||||
cl.Fields.Remove (class_ptr);
|
||||
|
||||
// step 2: remove init code from cctor
|
||||
RemoveCCtorInit (cl, class_ptr);
|
||||
|
||||
// step 3: patch every method
|
||||
PatchMethods (cl, class_ptr, classPtrField);
|
||||
}
|
||||
|
||||
void PatchMethods (TypeDefinition cl, FieldDefinition classPtr, FieldDefinition classPtrField)
|
||||
{
|
||||
foreach (var method in cl.Methods) {
|
||||
PatchMethod (method, classPtr, classPtrField);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchMethod (MethodDefinition method, FieldDefinition classPtr, FieldDefinition classPtrField)
|
||||
{
|
||||
foreach (var instr in method.Body.Instructions) {
|
||||
if (instr.OpCode == OpCodes.Ldsfld && instr.Operand == classPtr) {
|
||||
instr.Operand = classPtrField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCCtorInit (TypeDefinition cl, FieldDefinition class_ptr)
|
||||
{
|
||||
var cctor = cl.Methods.FirstOrDefault (m => m.Name == ".cctor");
|
||||
if (cctor is null)
|
||||
return; // no static init - should never happen, but we can deal.
|
||||
|
||||
var il = cctor.Body.GetILProcessor ();
|
||||
Instruction? stsfld = null;
|
||||
int i = 0;
|
||||
for (; i < il.Body.Instructions.Count; i++) {
|
||||
var instr = il.Body.Instructions [i];
|
||||
// look for
|
||||
// stsfld class_ptr
|
||||
if (instr.OpCode == OpCodes.Stsfld && instr.Operand == class_ptr) {
|
||||
stsfld = instr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stsfld is null)
|
||||
return;
|
||||
|
||||
// if we see:
|
||||
// ldstr "any"
|
||||
// call ObjCRuntime.GetClassHandle
|
||||
// stsfld class_ptr
|
||||
// Then we can remove all of those instructions
|
||||
|
||||
var isGetClassHandle = IsGetClassHandle (il, i - 1);
|
||||
var isLdStr = IsLdStr (il, i - 2);
|
||||
|
||||
if (isGetClassHandle && isLdStr) {
|
||||
il.RemoveAt (i);
|
||||
il.RemoveAt (i - 1);
|
||||
il.RemoveAt (i - 2);
|
||||
} else if (isGetClassHandle) {
|
||||
// don't know how the string got on the stack, so at least get rid of the
|
||||
// call to GetClassHandle by nopping it out. This still leaves the string on
|
||||
// the stack, so pop it.
|
||||
il.Replace (il.Body.Instructions [i - 1], Instruction.Create (OpCodes.Nop));
|
||||
// can't remove all three, so just pop the IntPtr.
|
||||
il.Replace (il.Body.Instructions [i], Instruction.Create (OpCodes.Pop));
|
||||
} else {
|
||||
// can't remove all three, so just pop the IntPtr.
|
||||
il.Replace (il.Body.Instructions [i], Instruction.Create (OpCodes.Pop));
|
||||
}
|
||||
|
||||
// if we're left with exactly 1 instruction and it's a return,
|
||||
// then we can get rid of the entire method
|
||||
if (cctor.Body.Instructions.Count == 1) {
|
||||
if (cctor.Body.Instructions.Last ().OpCode == OpCodes.Ret)
|
||||
cl.Methods.Remove (cctor);
|
||||
}
|
||||
|
||||
// if we're left with exactly 2 instructions and the first is a no-op
|
||||
// and the last is a return, then we can get rid of the entire method
|
||||
if (cctor.Body.Instructions.Count == 2) {
|
||||
if (cctor.Body.Instructions.Last ().OpCode == OpCodes.Ret &&
|
||||
cctor.Body.Instructions.First ().OpCode == OpCodes.Nop)
|
||||
cl.Methods.Remove (cctor);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsGetClassHandle (ILProcessor il, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return false;
|
||||
var instr = il.Body.Instructions [index]!;
|
||||
var operand = instr.Operand?.ToString () ?? "";
|
||||
return instr.OpCode == OpCodes.Call && operand.Contains ("Class::GetHandle", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
bool IsLdStr (ILProcessor il, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return false;
|
||||
var instr = il.Body.Instructions [index]!;
|
||||
return instr.OpCode == OpCodes.Ldstr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.IO;
|
||||
using Mono.Cecil;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ClassRedirector {
|
||||
public class SimpleAssemblyResolver : DefaultAssemblyResolver {
|
||||
public SimpleAssemblyResolver (params string [] filesOrDirectories)
|
||||
: base ()
|
||||
{
|
||||
foreach (var fileOrDirectory in filesOrDirectories) {
|
||||
if (File.Exists (fileOrDirectory)) {
|
||||
AddSearchDirectory (Path.GetDirectoryName (fileOrDirectory));
|
||||
} else if (Directory.Exists (fileOrDirectory)) {
|
||||
AddSearchDirectory (fileOrDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RootNamespace>class_redirector</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
|
||||
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\common\CSToObjCMap.cs">
|
||||
<Link>CSToObjCMap.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\common\ObjCNameIndex.cs">
|
||||
<Link>ObjCNameIndex.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\common\StaticRegistrarFile.cs">
|
||||
<Link>StaticRegistrarFile.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ClassRedirector {
|
||||
public class CSToObjCMap : Dictionary<string, ObjCNameIndex> {
|
||||
const string objMapName = "CSToObjCMap";
|
||||
const string elementName = "Element";
|
||||
const string csNameName = "CSName";
|
||||
public CSToObjCMap () : base ()
|
||||
{
|
||||
}
|
||||
|
||||
public static XElement ToXElement (CSToObjCMap map)
|
||||
{
|
||||
return new XElement (objMapName, Elements (map));
|
||||
}
|
||||
|
||||
static IEnumerable<XElement> Elements (CSToObjCMap map)
|
||||
{
|
||||
return map.Select (kvp => new XElement (elementName, new XAttribute (csNameName, kvp.Key), ObjCNameIndex.ToXElement (kvp.Value)));
|
||||
}
|
||||
|
||||
public static CSToObjCMap FromXElement (XElement xmap)
|
||||
{
|
||||
var map = new CSToObjCMap ();
|
||||
var elements = from el in xmap.Descendants (elementName)
|
||||
select new KeyValuePair<string?, ObjCNameIndex?> (el.Attribute (csNameName)?.Value,
|
||||
ObjCNameIndex.FromXElement (el.Element (ObjCNameIndex.ObjNameIndexName)));
|
||||
foreach (var elem in elements) {
|
||||
if (elem.Key is not null && elem.Value is not null)
|
||||
map.Add (elem.Key, elem.Value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static CSToObjCMap? FromXDocument (XDocument doc)
|
||||
{
|
||||
var el = doc.Descendants (objMapName).FirstOrDefault ();
|
||||
return el is null ? null : FromXElement (el);
|
||||
}
|
||||
|
||||
public static XDocument ToXDocument (CSToObjCMap map)
|
||||
{
|
||||
return new XDocument (ToXElement (map));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ClassRedirector {
|
||||
public class ObjCNameIndex {
|
||||
public const string ObjNameIndexName = "ObjNameIndex";
|
||||
const string nameName = "Name";
|
||||
const string indexName = "Index";
|
||||
public ObjCNameIndex (string objCName, int mapIndex)
|
||||
{
|
||||
ObjCName = objCName;
|
||||
MapIndex = mapIndex;
|
||||
}
|
||||
public string ObjCName { get; private set; }
|
||||
public int MapIndex { get; private set; }
|
||||
|
||||
public static XElement ToXElement (ObjCNameIndex nameIndex)
|
||||
{
|
||||
return new XElement (ObjNameIndexName,
|
||||
new XElement (nameName, nameIndex.ObjCName),
|
||||
new XElement (indexName, nameIndex.MapIndex));
|
||||
}
|
||||
|
||||
public static ObjCNameIndex? FromXElement (XElement? objNameIndex)
|
||||
{
|
||||
if (objNameIndex is null)
|
||||
return null;
|
||||
var name = (string?) objNameIndex.Element (nameName);
|
||||
var index = (int?) objNameIndex.Element (indexName);
|
||||
if (name is null || index is null)
|
||||
return null;
|
||||
return new ObjCNameIndex (name, index.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ClassRedirector {
|
||||
public class StaticRegistrarFile {
|
||||
public const string Name = "static-registrar-map.xml";
|
||||
}
|
||||
}
|
||||
|
Загрузка…
Ссылка в новой задаче