Skip to content

Instantly share code, notes, and snippets.

@azyobuzin
Created February 20, 2017 18:24
Show Gist options
  • Save azyobuzin/a069db2afe90b4b2494fc1136362b235 to your computer and use it in GitHub Desktop.
Save azyobuzin/a069db2afe90b4b2494fc1136362b235 to your computer and use it in GitHub Desktop.
Xamarin の base メソッドを呼ばなければいけない/呼んではいけない奴を抽出
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
namespace XamarinXmlDoc
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("呼ばなきゃダメ");
foreach (var x in EnumerateMethodsOverridingMethodsMustCall())
{
Console.WriteLine(x);
}
Console.WriteLine();
Console.WriteLine("呼んじゃダメ");
foreach (var x in EnumerateMethodsYouShoudNotCallBase())
{
Console.WriteLine(x);
}
}
private static string GetFrameworkDirectory()
{
var programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (string.IsNullOrEmpty(programFilesPath))
{
programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
}
return Path.Combine(programFilesPath, @"Reference Assemblies\Microsoft\Framework");
}
private static IEnumerable<MethodSignature> EnumerateMethodsOverridingMethodsMustCall()
{
var frameworkDir = GetFrameworkDirectory();
const string androidMustCallBasePattern = @"Derived\s*classes\s*must\s*call\s*through\s*to\s*the\s*super\s*class's\s*implementation\s*of\s*this\s*method\.";
const string iosMustCallBasePattern = @"Application\s*developers\s*who\s*override\s*this\s*method\s*must\s*call\s*base\.";
return EnumerateMethodDocuments(Path.Combine(frameworkDir, @"MonoAndroid\v7.0\Mono.Android.xml"))
.Where(x => Regex.IsMatch(x.DocumentContent, androidMustCallBasePattern))
.Concat(
EnumerateMethodDocuments(Path.Combine(frameworkDir, @"Xamarin.iOS\v1.0\Xamarin.iOS.xml"))
.Where(x => Regex.IsMatch(x.DocumentContent, iosMustCallBasePattern))
)
.Select(x =>
{
if (x.MethodName[x.MethodName.Length - 1] == ')')
{
var lparenIndex = x.MethodName.LastIndexOf('(');
var parameters = x.MethodName
.Substring(lparenIndex + 1, x.MethodName.Length - lparenIndex - 2)
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var lastDotIndex = x.MethodName.LastIndexOf('.', lparenIndex - 1);
return new MethodSignature(
x.MethodName.Remove(lastDotIndex),
x.MethodName.Substring(lastDotIndex + 1, lparenIndex - lastDotIndex - 1),
parameters
);
}
else
{
var lastDotIndex = x.MethodName.LastIndexOf('.');
return new MethodSignature(
x.MethodName.Remove(lastDotIndex),
x.MethodName.Substring(lastDotIndex + 1, x.MethodName.Length - lastDotIndex - 1),
Array.Empty<string>()
);
}
});
}
private static IEnumerable<MethodDocument> EnumerateMethodDocuments(string xmlFilePath)
{
using (var xmlReader = XmlReader.Create(File.OpenRead(xmlFilePath)))
{
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element
&& xmlReader.LocalName == "member")
{
var memberElm = (XElement)XNode.ReadFrom(xmlReader);
var nameAttr = memberElm.Attribute("name");
if (nameAttr == null) continue;
var name = nameAttr.Value;
if (!name.StartsWith("M:", StringComparison.Ordinal)) continue;
yield return new MethodDocument(name.Substring(2), memberElm.Value);
}
}
}
}
private struct MethodDocument
{
public string MethodName { get; }
public string DocumentContent { get; }
public MethodDocument(string methodName, string documentContent)
{
this.MethodName = methodName;
this.DocumentContent = documentContent;
}
}
private struct MethodSignature
{
public string TypeName { get; }
public string MethodName { get; }
public string[] ParameterTypes { get; }
public MethodSignature(string typeName, string methodName, string[] parameterTypes)
{
this.TypeName = typeName;
this.MethodName = methodName;
this.ParameterTypes = parameterTypes;
}
public override string ToString()
{
return $"{this.TypeName}::{this.MethodName}({string.Join(", ", this.ParameterTypes)})";
}
}
private static IEnumerable<MethodSignature> EnumerateMethodsYouShoudNotCallBase()
{
var dllPath = Path.Combine(GetFrameworkDirectory(), @"Xamarin.iOS\v1.0\Xamarin.iOS.dll");
var module = ModuleDefinition.ReadModule(dllPath);
var publicOrProtectedVirtualMethods = EnumerateAllPublicTypes(module)
.SelectMany(TypeDefinitionRocks.GetMethods)
.Where(x => x.IsVirtual && (x.Attributes & MethodAttributes.Family) != 0 && x.HasBody);
foreach (var method in publicOrProtectedVirtualMethods)
{
var instructions = method.Body.Instructions;
if (instructions.Count != 2) continue;
var firstInst = instructions[0];
if (firstInst.OpCode != OpCodes.Newobj) continue;
var ctorRef = firstInst.Operand as MethodReference;
if (ctorRef?.DeclaringType.FullName != "Foundation.You_Should_Not_Call_base_In_This_Method") continue;
if (instructions[1].OpCode == OpCodes.Throw)
{
yield return new MethodSignature(
method.DeclaringType.FullName,
method.Name,
method.Parameters.Select(x => x.ParameterType.FullName).ToArray()
);
}
}
}
private static IEnumerable<TypeDefinition> EnumerateAllPublicTypes(ModuleDefinition module)
{
foreach (var x in module.Types)
{
if (x.IsPublic)
{
yield return x;
foreach (var y in EnumerateAllNestedPublicTypes(x))
{
yield return y;
}
}
}
}
private static IEnumerable<TypeDefinition> EnumerateAllNestedPublicTypes(TypeDefinition type)
{
foreach (var x in type.NestedTypes)
{
if (x.IsNestedPublic)
{
yield return x;
foreach (var y in EnumerateAllNestedPublicTypes(x))
{
yield return y;
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment