Created
February 20, 2017 18:24
-
-
Save azyobuzin/a069db2afe90b4b2494fc1136362b235 to your computer and use it in GitHub Desktop.
Xamarin の base メソッドを呼ばなければいけない/呼んではいけない奴を抽出
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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