Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Importer and SigComparer‘s handling of generic when must treat generic type definition as generic instantiation type. #466

Merged
merged 1 commit into from
May 29, 2022

Conversation

wwh1004
Copy link
Contributor

@wwh1004 wwh1004 commented May 28, 2022

  1. Fix Importer and SigComparer‘s handling of generic when must treat generic type definition as generic instantiation type.
  2. Mark Importer.ImportDeclaringType(Type) as obsolete.

Closes #462, Closes #463, Closes #464, Closes #465

Test code (run all gac assemblies and built-in test cases, include MemberRef table, Field table, Method table, MethodSpec table, Property table, Event table):

public static class Program {
	static IEnumerable<string> GetAssemblyPaths() {
		var gacBasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"Microsoft.NET\assembly");
		var gacPaths = new[] { Path.Combine(gacBasePath, "GAC_MSIL")/*, Path.Combine(gacBasePath, "GAC_64"), Path.Combine(gacBasePath, "GAC_32")*/ };
		//var gacPaths = new[] { @"C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.5" };
		var assemblies = new Dictionary<string, string>();
		foreach (var path in gacPaths.SelectMany(t => Directory.EnumerateFiles(t, "*", SearchOption.AllDirectories))) {
			var name = Path.GetFileName(path).ToLowerInvariant();
			if (name.EndsWith(".dll") && !assemblies.ContainsKey(name))
				assemblies.Add(name, path);
		}
		return assemblies.Values;
	}

	static void Main() {
		var list = GetAssemblyPaths().ToList();
		list.Add(typeof(Program).Assembly.Location);
		list.Add(typeof(ModuleDef).Assembly.Location);
		using var progress = new Progress(list.Count);
		Console.WriteLine(list.Count);
		var sw = Stopwatch.StartNew();
		Parallel.ForEach(list, t => {
			try {
				var assembly = Assembly.LoadFile(t);
				Test_Importer_SigComparer(assembly.ManifestModule);
			}
			catch (FileLoadException) {
			}
			catch (FileNotFoundException) {
			}
			progress.Increase();
		});
		sw.Stop();

		Console.WriteLine($"Pass: {sw.ElapsedMilliseconds / 1000d:f2}s");
		Console.ReadKey(true);
	}

	static void Test_Importer_SigComparer(Module module) {
		const BindingFlags BindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;

		using var moduleDef = ModuleDefMD.Load(module, ModuleDef.CreateModuleContext());
		if (IsMixedAssembly(moduleDef))
			return;
		if (moduleDef.Name == "System.Runtime.WindowsRuntime.dll")
			return;

		var importer1 = new Importer(moduleDef, ImporterOptions.TryToUseExistingAssemblyRefs);
		var importer2 = new Importer(moduleDef, ImporterOptions.TryToUseDefs | ImporterOptions.TryToUseExistingAssemblyRefs);
		var comparer = new SigComparer(SigComparerOptions.DontCompareTypeScope | SigComparerOptions.CompareMethodFieldDeclaringType | SigComparerOptions.IgnoreModifiers);

		foreach (var field in moduleDef.Enumerate<MemberRef>(t => t.IsFieldRef).Cast<IField>().Concat(moduleDef.Enumerate<FieldDef>())) {
			if ((field as IMemberDef)?.DeclaringType.Name == "__TransparentProxy")
				continue;

			FieldInfo fieldInfo;
			try {
				fieldInfo = module.ResolveField(field.MDToken.ToInt32());
			}
			catch {
				continue;
			}
			var importedField1 = importer1.Import(fieldInfo);
			var importedField2 = importer2.Import(fieldInfo);

			int h1 = comparer.GetHashCode(field);
			int h2 = comparer.GetHashCode(fieldInfo);
			int h3 = comparer.GetHashCode(importedField1);
			int h4 = comparer.GetHashCode(importedField2);
			bool b1 = field is not IMemberDef || importedField2 is IMemberDef;
			bool b2 = h1 == h2 && h2 == h3 && h3 == h4;
			bool b3 = comparer.Equals(field, fieldInfo);
			bool b4 = comparer.Equals(field, importedField1);
			bool b5 = comparer.Equals(field, importedField2);
			bool b6 = comparer.Equals(fieldInfo, importedField1);
			bool b7 = comparer.Equals(fieldInfo, importedField2);
			bool b8 = comparer.Equals(importedField1, importedField2);
			bool b = b1 && b2 && b3 && b4 && b5 && b6 && b7 && b8;
			Debug.Assert(b, moduleDef.Name + Environment.NewLine + field.FullName);
		}

		foreach (var method in moduleDef.Enumerate<MemberRef>(t => t.IsMethodRef).Cast<IMethod>().Concat(moduleDef.Enumerate<MethodSpec>()).Concat(moduleDef.Enumerate<MethodDef>())) {
			if ((method as IMemberDef)?.DeclaringType.Name == "__TransparentProxy")
				continue;
			if (method.DeclaringType is TypeDef td && td.IsImport && method.Name.StartsWith("_VtblGap", StringComparison.Ordinal))
				continue;
			if (method is MemberRef mr && !mr.Signature.IsVarArg && mr.Class is MethodDef md && (md.DeclaringType.IsImport || md.DeclaringType.CustomAttributes.Any(t => t.TypeFullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute")))
				continue; // A strange MemberRef, MemberRef.Class is a MethodDef but this method is not varargs. It seems contains in office interop dlls.

			MethodBase methodInfo;
			try {
				methodInfo = module.ResolveMethod(method.MDToken.ToInt32());
			}
			catch {
				continue;
			}
			var importedMethod1 = importer1.Import(methodInfo);
			var importedMethod2 = importer2.Import(methodInfo);

			int h1 = comparer.GetHashCode(method);
			int h2 = comparer.GetHashCode(methodInfo);
			int h3 = comparer.GetHashCode(importedMethod1);
			int h4 = comparer.GetHashCode(importedMethod2);
			bool b1 = method is not IMemberDef || importedMethod2 is IMemberDef;
			bool b2 = h1 == h2 && h2 == h3 && h3 == h4;
			bool b3 = comparer.Equals(method, methodInfo);
			bool b4 = comparer.Equals(method, importedMethod1);
			bool b5 = comparer.Equals(method, importedMethod2);
			bool b6 = comparer.Equals(methodInfo, importedMethod1);
			bool b7 = comparer.Equals(methodInfo, importedMethod2);
			bool b8 = comparer.Equals(importedMethod1, importedMethod2);
			bool b = b1 && b2 && b3 && b4 && b5 && b6 && b7 && b8;
			Debug.Assert(b, moduleDef.Name + Environment.NewLine + method.FullName);
		}

		foreach (var property in moduleDef.Enumerate<PropertyDef>()) {
			var propertyInfo = module.ResolveType(property.DeclaringType.MDToken.ToInt32()).GetProperties(BindingAttr).First(t => t.MetadataToken == property.MDToken.ToInt32());
			var importedSig1 = importer1.Import(property.PropertySig);
			var importedSig2 = importer2.Import(property.PropertySig);

			int h1 = comparer.GetHashCode(property);
			int h2 = comparer.GetHashCode(propertyInfo);
			int h3 = comparer.GetHashCode(property.PropertySig);
			int h4 = comparer.GetHashCode(importedSig1);
			int h5 = comparer.GetHashCode(importedSig2);
			bool b1 = h1 == h2 && h3 == h4 && h4 == h5;
			bool b2 = comparer.Equals(property, propertyInfo);
			bool b3 = comparer.Equals(property.PropertySig, importedSig1);
			bool b4 = comparer.Equals(property.PropertySig, importedSig2);
			bool b5 = comparer.Equals(importedSig1, importedSig2);
			bool b = b1 && b2 && b3 && b4 && b5;
			Debug.Assert(b, moduleDef.Location + Environment.NewLine + property.FullName);
		}

		foreach (var @event in moduleDef.Enumerate<EventDef>()) {
			var eventInfo = module.ResolveType(@event.DeclaringType.MDToken.ToInt32()).GetEvents(BindingAttr).First(t => t.MetadataToken == @event.MDToken.ToInt32());

			int h1 = comparer.GetHashCode(@event);
			int h2 = comparer.GetHashCode(eventInfo);
			bool b1 = h1 == h2;
			bool b2 = comparer.Equals(@event, eventInfo);
			bool b = b1 && b2;
			Debug.Assert(b, moduleDef.Name + Environment.NewLine + @event.FullName);
		}
	}
}

static class ModuleDefExtensions {
	static readonly Dictionary<Type, Table> tableMap = new() {
		{ typeof(ModuleDef), Table.Module },
		{ typeof(TypeRef), Table.TypeRef },
		{ typeof(TypeDef), Table.TypeDef },
		{ typeof(FieldDef), Table.Field },
		{ typeof(MethodDef), Table.Method },
		{ typeof(ParamDef), Table.Param },
		{ typeof(InterfaceImpl), Table.InterfaceImpl },
		{ typeof(MemberRef), Table.MemberRef },
		{ typeof(Constant), Table.Constant },
		{ typeof(DeclSecurity), Table.DeclSecurity },
		{ typeof(ClassLayout), Table.ClassLayout },
		{ typeof(StandAloneSig), Table.StandAloneSig },
		{ typeof(EventDef), Table.Event },
		{ typeof(PropertyDef), Table.Property },
		{ typeof(ModuleRef), Table.ModuleRef },
		{ typeof(TypeSpec), Table.TypeSpec },
		{ typeof(ImplMap), Table.ImplMap },
		{ typeof(AssemblyDef), Table.Assembly },
		{ typeof(AssemblyRef), Table.AssemblyRef },
		{ typeof(FileDef), Table.File },
		{ typeof(ExportedType), Table.ExportedType },
		{ typeof(ManifestResource), Table.ManifestResource },
		{ typeof(GenericParam), Table.GenericParam },
		{ typeof(MethodSpec), Table.MethodSpec },
		{ typeof(GenericParamConstraint), Table.GenericParamConstraint }
	};

	public static IEnumerable<T> Enumerate<T>(this ModuleDef module) {
		return Enumerate<T>(module, null);
	}

	public static IEnumerable<T> Enumerate<T>(this ModuleDef module, Predicate<T>? predicate) {
		if (module is null)
			yield break;
		if (!tableMap.TryGetValue(typeof(T), out var table))
			yield break;
		for (uint rid = 1; ; rid++) {
			if (module.ResolveToken(new MDToken(table, rid)) is not T t)
				yield break;
			if (predicate is null || predicate(t))
				yield return t;
		}
	}
}

public class Class1 {
	public object Field1 = new();
	public Class1 Field2 = new();
	public Class2<object> Field3 = new();
	public Class2<Class1> Field4 = new();
	public Class3<object, Class1> Field5 = new();
	public Class3<Class2<Class1>, Class1> Field6 = new();

	public object Property1 => Field1;
	public Class1 Property2 => Field2;
	public Class2<object> Property3 => Field3;
	public Class2<Class1> Property4 => Field4;
	public Class3<object, Class1> Property5 => Field5;
	public Class3<Class2<Class1>, Class1> Property6 => Field6;

	public event EventHandler<object>? Event1;
	public event EventHandler<Class1>? Event2;
	public event EventHandler<Class2<object>>? Event3;
	public event EventHandler<Class2<Class1>>? Event4;
	public event EventHandler<Class3<object, Class1>>? Event5;
	public event EventHandler<Class3<Class2<Class1>, Class1>>? Event6;

	public object Method1(object a) => Method1(a);
	public Class1 Method2(Class1 a) => Method2(a);
	public Class2<object> Method3(Class2<object> a) => Method3(a);
	public Class2<Class1> Method4(Class2<Class1> a) => Method4(a);
	public Class3<object, Class1> Method5(Class3<object, Class1> a) => Method5(a);
	public Class3<Class2<Class1>, Class1> Method6(Class3<Class2<Class1>, Class1> a) => Method6(a);

	public class Class1_1<T1_1_A> {
		public object Field1 = new();
		public Class1_1<object> Field2 = new();
		public Class1_1<T1_1_A> Field3 = new();
		public Class1_1<Class1_1<T1_1_A>> Field4 = new();

		public object Property1 => Field1;
		public Class1_1<object> Property2 => Field2;
		public Class1_1<T1_1_A> Property3 => Field3;
		public Class1_1<Class1_1<T1_1_A>> Property4 => Field4;

		public EventHandler<object>? Event1;
		public EventHandler<Class1_1<object>>? Event2;
		public EventHandler<Class1_1<T1_1_A>>? Event3;
		public EventHandler<Class1_1<Class1_1<T1_1_A>>>? Event4;

		public object Method1(object a) => Method1(a);
		public Class1_1<object> Method2(Class1_1<object> a) => Method2(a);
		public Class1_1<T1_1_A> Method3(Class1_1<T1_1_A> a) => Method3(a);
		public Class1_1<Class1_1<T1_1_A>> Method4(Class1_1<Class1_1<T1_1_A>> a) => Method4(a);

		public class Class1_1_1<T1_1_1_A> {
			public object Field1 = new();
			public Class1_1<object> Field2 = new();
			public Class1_1<T1_1_A> Field3 = new();
			public Class1_1_1<object> Field4 = new();
			public Class1_1_1<T1_1_A> Field5 = new();
			public Class1_1_1<T1_1_1_A> Field6 = new();
			public Class1_1_1<Class1_1_1<T1_1_A>> Field7 = new();
			public Class1_1_1<Class1_1_1<T1_1_1_A>> Field8 = new();

			public object Property1 => Field1;
			public Class1_1<object> Property2 => Field2;
			public Class1_1<T1_1_A> Property3 => Field3;
			public Class1_1_1<object> Property4 => Field4;
			public Class1_1_1<T1_1_A> Property5 => Field5;
			public Class1_1_1<T1_1_1_A> Property6 => Field6;
			public Class1_1_1<Class1_1_1<T1_1_A>> Property7 => Field7;
			public Class1_1_1<Class1_1_1<T1_1_1_A>> Property8 => Field8;

			public EventHandler<object>? Event1;
			public EventHandler<Class1_1<object>>? Event2;
			public EventHandler<Class1_1<T1_1_A>>? Event3;
			public EventHandler<Class1_1_1<object>>? Event4;
			public EventHandler<Class1_1_1<T1_1_A>>? Event5;
			public EventHandler<Class1_1_1<T1_1_1_A>>? Event6;
			public EventHandler<Class1_1_1<Class1_1_1<T1_1_A>>>? Event7;
			public EventHandler<Class1_1_1<Class1_1_1<T1_1_1_A>>>? Event8;

			public object Method1(object a) => Method1(a);
			public Class1_1<object> Method2(Class1_1<object> a) => Method2(a);
			public Class1_1<T1_1_A> Method3(Class1_1<T1_1_A> a) => Method3(a);
			public Class1_1_1<object> Method4(Class1_1_1<object> a) => Method4(a);
			public Class1_1_1<T1_1_A> Method5(Class1_1_1<T1_1_A> a) => Method5(a);
			public Class1_1_1<T1_1_1_A> Method6(Class1_1_1<T1_1_1_A> a) => Method6(a);
			public Class1_1_1<Class1_1_1<T1_1_A>> Method7(Class1_1_1<Class1_1_1<T1_1_A>> a) => Method7(a);
			public Class1_1_1<Class1_1_1<T1_1_1_A>> Method8(Class1_1_1<Class1_1_1<T1_1_1_A>> a) => Method8(a);
		}
	}
}

public class Class2<T2_A> {
	public object Field1 = new();
	public Class1 Field2 = new();
	public Class2<object> Field3 = new();
	public Class2<Class1> Field4 = new();
	public Class3<object, Class1> Field5 = new();
	public Class3<Class2<Class1>, Class1> Field6 = new();
	public Class2<T2_A> Field7 = new();
	public Class2<Class2<T2_A>> Field8 = new();

	public object Property1 => Field1;
	public Class1 Property2 => Field2;
	public Class2<object> Property3 => Field3;
	public Class2<Class1> Property4 => Field4;
	public Class3<object, Class1> Property5 => Field5;
	public Class3<Class2<Class1>, Class1> Property6 => Field6;
	public Class2<T2_A> Property7 => Field7;
	public Class2<Class2<T2_A>> Property8 => Field8;

	public EventHandler<object>? Event1;
	public EventHandler<Class1>? Event2;
	public EventHandler<Class2<object>>? Event3;
	public EventHandler<Class2<Class1>>? Event4;
	public EventHandler<Class3<object, Class1>>? Event5;
	public EventHandler<Class3<Class2<Class1>, Class1>>? Event6;
	public EventHandler<Class2<T2_A>>? Event7;
	public EventHandler<Class2<Class2<T2_A>>>? Event8;

	public object Method1(object a) => Method1(a);
	public Class1 Method2(Class1 a) => Method2(a);
	public Class2<object> Method3(Class2<object> a) => Method3(a);
	public Class2<Class1> Method4(Class2<Class1> a) => Method4(a);
	public Class3<object, Class1> Method5(Class3<object, Class1> a) => Method5(a);
	public Class3<Class2<Class1>, Class1> Method6(Class3<Class2<Class1>, Class1> a) => Method6(a);
	public Class2<T2_A> Method7(Class2<T2_A> a) => Method7(a);
	public Class2<Class2<T2_A>> Method8(Class2<Class2<T2_A>> a) => Method8(a);
}

public class Class3<T3_A, T3_B> {
	public object Field1 = new();
	public Class1 Field2 = new();
	public Class2<object> Field3 = new();
	public Class2<Class1> Field4 = new();
	public Class3<object, Class1> Field5 = new();
	public Class3<Class2<Class1>, Class1> Field6 = new();
	public Class3<T3_A, T3_B> Field7 = new();
	public Class3<Class3<T3_A, T3_B>, T3_B> Field8 = new();

	public object Property1 => Field1;
	public Class1 Property2 => Field2;
	public Class2<object> Property3 => Field3;
	public Class2<Class1> Property4 => Field4;
	public Class3<object, Class1> Property5 => Field5;
	public Class3<Class2<Class1>, Class1> Property6 => Field6;
	public Class3<T3_A, T3_B> Property7 => Field7;
	public Class3<Class3<T3_A, T3_B>, T3_B> Property8 => Field8;

	public EventHandler<object>? Event1;
	public EventHandler<Class1>? Event2;
	public EventHandler<Class2<object>>? Event3;
	public EventHandler<Class2<Class1>>? Event4;
	public EventHandler<Class3<object, Class1>>? Event5;
	public EventHandler<Class3<Class2<Class1>, Class1>>? Event6;
	public EventHandler<Class3<T3_A, T3_B>>? Event7;
	public EventHandler<Class3<Class3<T3_A, T3_B>, T3_B>>? Event8;

	public object Method1(object a) => Method1(a);
	public Class1 Method2(Class1 a) => Method2(a);
	public Class2<object> Method3(Class2<object> a) => Method3(a);
	public Class2<Class1> Method4(Class2<Class1> a) => Method4(a);
	public Class3<object, Class1> Method5(Class3<object, Class1> a) => Method5(a);
	public Class3<Class2<Class1>, Class1> Method6(Class3<Class2<Class1>, Class1> a) => Method6(a);
	public Class3<T3_A, T3_B> Method7(Class3<T3_A, T3_B> a) => Method7(a);
	public Class3<Class3<T3_A, T3_B>, T3_B> Method8(Class3<Class3<T3_A, T3_B>, T3_B> a) => Method8(a);
}

…neric type definition as generic instantiation type.

Mark Importer.ImportDeclaringType(Type) as obsolete.
@wwh1004
Copy link
Contributor Author

wwh1004 commented May 28, 2022

A tab was formatted to some spaces. So I force push a new one.

@wtfsck
Copy link
Contributor

wtfsck commented May 29, 2022

cc: @CreateAndInject @ElektroKill : can you test this and see if it fixes the problems you found in #462

@CreateAndInject
Copy link
Contributor

cc: @CreateAndInject @ElektroKill : can you test this and see if it fixes the problems you found in #462

Yes, it fixed my problem.

@ElektroKill
Copy link
Contributor

cc: @CreateAndInject @ElektroKill : can you test this and see if it fixes the problems you found in #462

Testing using various samples resulted in proper results and the test case in the issue now returns the proper result.

@wtfsck wtfsck merged commit 7cad105 into 0xd4d:master May 29, 2022
@wtfsck
Copy link
Contributor

wtfsck commented May 29, 2022

Thanks, merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants