using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; using SpaceFlint.JavaBinary; using SpaceFlint.CilToJava; public class DotNetImporter { ModuleDefinition module; Dictionary typeMap; HashSet typeUse; HashSet funcIfcNames; HashSet funcIfcTypes; TypeReference systemException; MethodReference methodRefNotImplementedException; MethodBody methodBodyNotImplementedExceptionThrow; MethodReference retainNameAttributeConstructor; MethodReference asInterfaceAttributeConstructor; JavaException.Where Where; public DotNetImporter(ModuleDefinition _module) { module = _module; typeMap = new Dictionary(); typeUse = new HashSet(); funcIfcNames = new HashSet(); funcIfcTypes = new HashSet(); var typeSystem = module.TypeSystem; typeMap["./" + TypeCode.Empty] = typeSystem.Void; typeMap["./" + TypeCode.Boolean] = typeSystem.Boolean; typeMap["./" + TypeCode.Byte] = typeSystem.Byte; typeMap["./" + TypeCode.SByte] = typeSystem.SByte; typeMap["./" + TypeCode.Char] = typeSystem.Char; typeMap["./" + TypeCode.Int16] = typeSystem.Int16; typeMap["./" + TypeCode.Int32] = typeSystem.Int32; typeMap["./" + TypeCode.Int64] = typeSystem.Int64; typeMap["./" + TypeCode.UInt16] = typeSystem.UInt16; typeMap["./" + TypeCode.UInt32] = typeSystem.UInt32; typeMap["./" + TypeCode.UInt64] = typeSystem.UInt64; typeMap["./" + TypeCode.Single] = typeSystem.Single; typeMap["./" + TypeCode.Double] = typeSystem.Double; typeMap["java.lang.Object"] = typeSystem.Object; typeMap["java.lang.String"] = typeSystem.String; systemException = new TypeReference( "System", "Exception", module, typeSystem.CoreLibrary); methodRefNotImplementedException = new MethodReference( ".ctor", typeSystem.Void, new TypeReference( "System", "NotImplementedException", module, typeSystem.CoreLibrary)); methodRefNotImplementedException.HasThis = true; Where = new JavaException.Where(); Where.Push($"assembly '{module.Assembly.Name.Name}'"); ImportCilTypes(module.Types); } void ImportCilTypes(Mono.Collections.Generic.Collection types) { foreach (var cilType in types) { var jvmName = cilType.FullName.Replace('/', '$'); if (typeMap.ContainsKey(jvmName)) throw new JavaException($"duplicate class/type {jvmName}", Where); typeMap[jvmName] = cilType; if (cilType.HasNestedTypes) ImportCilTypes(cilType.NestedTypes); } CreateCustomAttribute("DiscardAttribute", out var discardAttribute, types); CreateCustomAttribute("RetainNameAttribute", out var retainNameAttribute, types); CreateCustomAttribute("RetainTypeAttribute", out var retainTypeAttribute, types); CreateCustomAttribute("AsInterfaceAttribute", out var asInterfaceAttribute, types); retainNameAttributeConstructor = GetParameterlessConstructor(retainNameAttribute); asInterfaceAttributeConstructor = GetParameterlessConstructor(asInterfaceAttribute); } void CreateCustomAttribute(string attrName, out TypeReference attrType, Mono.Collections.Generic.Collection types) { attrName = "java.attr." + attrName; if (typeMap.TryGetValue(attrName, out attrType)) typeMap.Remove(attrName); else { var attrTypeDef = CreateCustomAttribute(attrName); types.Add(attrTypeDef); attrType = attrTypeDef; } } TypeDefinition CreateCustomAttribute(string typeName) { int dot = typeName.LastIndexOf('.'); string nsName = typeName.Substring(0, dot); typeName = typeName.Substring(dot + 1); var typeSystem = module.TypeSystem; // create a custom attribute type var newType = new TypeDefinition(nsName, typeName, TypeAttributes.Public | TypeAttributes.BeforeFieldInit); // which inherits from System.Attribute newType.BaseType = new TypeReference("System", "Attribute", module, typeSystem.CoreLibrary); // and specifies [System.AttributeUsageAttribute( ... var usageConstructor = new MethodReference(".ctor", typeSystem.Void, new TypeReference( "System", "AttributeUsageAttribute", module, typeSystem.CoreLibrary)); usageConstructor.HasThis = true; usageConstructor.Parameters.Add(new ParameterDefinition( new TypeReference("System", "AttributeTargets", module, typeSystem.CoreLibrary, true))); // ... System.AttributeTargets.All, Inherited = false, AllowMultiple = false)] newType.CustomAttributes.Add(new CustomAttribute(usageConstructor, new byte[] { 0x01, 0x00, 0xFF, 0x7F, 0x00, 0x00, 0x01, 0x00, 0x54, 0x02, 0x09, 0x49, 0x6E, 0x68, 0x65, 0x72, 0x69, 0x74, 0x65, 0x64, 0x00, 0x54, 0x02, 0x0D, 0x41, 0x6C, 0x6C, 0x6F, 0x77, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x00 })); // now create a constructor for the new custom attribute var baseConstructor = new MethodReference(".ctor", typeSystem.Void, newType.BaseType); baseConstructor.HasThis = true; var newConstructor = new MethodDefinition(".ctor", (MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName), typeSystem.Void); var body = new MethodBody(newConstructor); body.GetILProcessor().Emit(Mono.Cecil.Cil.OpCodes.Ldarg_0); body.GetILProcessor().Emit(Mono.Cecil.Cil.OpCodes.Call, baseConstructor); body.GetILProcessor().Emit(Mono.Cecil.Cil.OpCodes.Ret); newConstructor.Body = body; newType.Methods.Add(newConstructor); return newType; } MethodReference GetParameterlessConstructor(TypeReference typeRef) { var typeDef = typeRef.Resolve(); foreach (var method in typeDef.Methods) { if (method.IsConstructor && method.Parameters.Count == 0) return method; } throw new ArgumentException("Parameterless constructor not found for type " + typeRef); } public void Merge(List classes) { var classes2 = new HashSet(classes.Count); foreach (var jclass in classes) { if (CreateCilTypeForClass(jclass)) classes2.Add(jclass); if ( jclass.Flags == ( JavaAccessFlags.ACC_INTERFACE | JavaAccessFlags.ACC_ABSTRACT | JavaAccessFlags.ACC_PUBLIC) && jclass.Methods != null && jclass.Methods.Count == 1) { // add interface to set of potentially functional interfaces funcIfcNames.Add(jclass.Name); } } // arrange Java inner classes as CIL nested classes, // and add Java outer classes to the CIL module itself foreach (var jclass in classes2) { Where.Push($"class '{jclass.Name}'"); var cilType = typeMap[jclass.Name] as TypeDefinition; AddClassToDeclaringType(cilType, jclass); Where.Pop(); } // establish links between classes and their // super classes and interfaces foreach (var jclass in classes2) { Where.Push($"class '{jclass.Name}'"); var cilType = typeMap[jclass.Name] as TypeDefinition; LinkCilTypesByClass(cilType, jclass); BuildCilTypeFromClass(cilType, jclass); Where.Pop(); } // discard private types that are not referenced foreach (var cilType in typeMap.Values) { var cilTypeDef = cilType.Resolve(); var visibility = cilTypeDef.Attributes & TypeAttributes.VisibilityMask; if ( visibility == TypeAttributes.NotPublic || visibility == TypeAttributes.NestedPrivate) { if (! typeUse.Contains(cilType)) { if (cilTypeDef.DeclaringType == null) module.Types.Remove(cilTypeDef); else cilTypeDef.DeclaringType.NestedTypes.Remove(cilTypeDef); } } } // create artificial delegate types for functional interfaces foreach (var functionalInterface in funcIfcTypes) { Where.Push($"functional interface '{functionalInterface}'"); BuildDelegate(functionalInterface); Where.Pop(); } } public void BuildDelegate(TypeDefinition fromInterface) { if (! fromInterface.HasMethods) return; var fromMethod = fromInterface.Methods[0]; if ((! fromMethod.IsPublic) || (! fromMethod.IsAbstract)) return; MethodAttributes attrs = 0; var newType = new TypeDefinition("", "Delegate", TypeAttributes.Public | TypeAttributes.Sealed); newType.CustomAttributes.Add(new CustomAttribute(asInterfaceAttributeConstructor)); newType.BaseType = new TypeReference( "System", "MulticastDelegate", module, module.TypeSystem.CoreLibrary); attrs = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; var newConst = new MethodDefinition(".ctor", attrs, module.TypeSystem.Void); newConst.Parameters.Add(new ParameterDefinition("object", 0, module.TypeSystem.Object)); newConst.Parameters.Add(new ParameterDefinition("method", 0, module.TypeSystem.IntPtr)); SetCommonMethodBody(newConst); attrs = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot; var newMethod = new MethodDefinition("Invoke", attrs, fromMethod.ReturnType); foreach (var fromMethodParm in fromMethod.Parameters) newMethod.Parameters.Add(fromMethodParm); newMethod.HasThis = true; SetCommonMethodBody(newMethod); attrs = MethodAttributes.Public | MethodAttributes.HideBySig; var newGetter = new MethodDefinition("AsInterface", attrs, fromInterface); newGetter.HasThis = true; SetCommonMethodBody(newGetter); newType.Methods.Add(newConst); newType.Methods.Add(newMethod); newType.Methods.Add(newGetter); fromInterface.NestedTypes.Add(newType); } public bool CreateCilTypeForClass(JavaClass jclass) { var name = jclass.Name; if ( typeMap.TryGetValue(name, out _) || jclass.IsInnerClass() && ( name.StartsWith("java.lang.Object$") || name.StartsWith("java.lang.String$"))) { Console.WriteLine($"skipping duplicate class '{name}'"); return false; } string nsName, clsName; TypeAttributes attrs = 0; if (jclass.IsInnerClass()) { nsName = string.Empty; clsName = jclass.OuterAndInnerClasses[0].InnerShortName; if (! string.IsNullOrEmpty(clsName)) { int idx = name.LastIndexOf('$'); if (idx != -1 && idx + 1 < name.Length) clsName = name.Substring(idx + 1); } if (string.IsNullOrEmpty(clsName) || (! Char.IsLetter(clsName[0]))) clsName = "autogenerated_" + jclass.GetHashCode().ToString("X8"); if ((jclass.Flags & JavaAccessFlags.ACC_PUBLIC) != 0) attrs = TypeAttributes.NestedPublic; else if ((jclass.Flags & JavaAccessFlags.ACC_PRIVATE) != 0) attrs = TypeAttributes.NestedPrivate; else if ((jclass.Flags & JavaAccessFlags.ACC_PROTECTED) != 0) attrs = TypeAttributes.NestedFamORAssem; else attrs = TypeAttributes.NestedAssembly; } else { int n = jclass.PackageNameLength; nsName = name.Substring(0, n); if (name[n] == '.') n++; clsName = name.Substring(n); if ((jclass.Flags & JavaAccessFlags.ACC_PUBLIC) != 0) attrs |= TypeAttributes.Public; } if ((jclass.Flags & JavaAccessFlags.ACC_ABSTRACT) != 0) attrs |= TypeAttributes.Abstract; if ((jclass.Flags & JavaAccessFlags.ACC_INTERFACE) != 0) attrs |= TypeAttributes.Interface; if ((jclass.Flags & JavaAccessFlags.ACC_FINAL) != 0) attrs |= TypeAttributes.Sealed; var newType = new TypeDefinition(nsName, clsName, attrs); newType.CustomAttributes.Add(new CustomAttribute(retainNameAttributeConstructor)); typeMap[name] = newType; return true; } public void AddClassToDeclaringType(TypeDefinition cilType, JavaClass jclass) { if (jclass.IsInnerClass()) { var outerName = jclass.OuterAndInnerClasses[0].OuterLongName; if ( typeMap.TryGetValue(outerName, out var cilOuterTypeRef) && cilOuterTypeRef is TypeDefinition cilOuterType) { foreach (var nestedType in cilOuterType.NestedTypes) { if (nestedType.Name == cilType.Name) { Console.WriteLine($"skipping duplicate class '{nestedType.FullName}'"); return; } } cilOuterType.NestedTypes.Add(cilType); } else { throw new JavaException( $"outer class '{outerName}' not found for inner class", Where); } } else { module.Types.Add(cilType); } } public void LinkCilTypesByClass(TypeDefinition cilType, JavaClass jclass) { var superName = jclass.Super; if (superName == "java.lang.Enum") superName = null; if (superName != null) { if (typeMap.TryGetValue(superName, out var cilSuperTypeRef)) { if (cilType.IsInterface) { if (superName != "java.lang.Object") { cilType.Interfaces.Add( new InterfaceImplementation(cilSuperTypeRef)); } if (jclass.Name == "java.lang.AutoCloseable") { // make java.lang.AutoCloseable extend System.IDisposable, // to allow use of imported classes in "using" statement. // see also ConvertInterfaceCall in CodeCall module. var iDisposableRef = new TypeReference( "System", "IDisposable", module, module.TypeSystem.CoreLibrary); cilType.Interfaces.Add( new InterfaceImplementation(iDisposableRef)); } } else { if ( jclass.Name == "java.lang.Throwable" && superName == "java.lang.Object") { cilSuperTypeRef = systemException; } cilType.BaseType = cilSuperTypeRef; } CilTypeAddUse(cilSuperTypeRef); } else throw new JavaException($"super class '{superName}' not found", Where); } if (jclass.Interfaces != null) { foreach (var interfaceName in jclass.Interfaces) { if (typeMap.TryGetValue(interfaceName, out var cilInterfaceTypeRef)) { if (cilInterfaceTypeRef.Resolve()?.IsInterface == true) { cilType.Interfaces.Add( new InterfaceImplementation(cilInterfaceTypeRef)); CilTypeAddUse(cilInterfaceTypeRef); } } else throw new JavaException($"interface '{interfaceName}' not found", Where); } } } void BuildCilTypeFromClass(TypeDefinition cilType, JavaClass jclass) { if (jclass.Fields != null) { foreach (var jfield in jclass.Fields) { if (IsPublicOrProtected(jfield.Flags)) { Where.Push($"field '{jfield.Name}'"); var cilField = BuildCilField(cilType, jfield); cilType.Fields.Add(cilField); Where.Pop(); } } } if (jclass.Methods != null) { foreach (var jmethod in jclass.Methods) { if (IsPublicOrProtected(jmethod.Flags)) { Where.Push($"method '{jmethod.Name}'"); // discard Java 8 default interface methods and static interface methods bool skip = ((jclass.Flags & JavaAccessFlags.ACC_INTERFACE) != 0) && ( ((jmethod.Flags & JavaAccessFlags.ACC_ABSTRACT) == 0) || ((jmethod.Flags & JavaAccessFlags.ACC_STATIC) != 0)); // discard overriding bridge methods that merely change the return type if (! skip) skip = SkipMethodByReturnType(jmethod); if (! skip) { var cilMethod = BuildCilMethod(cilType, jmethod); if (cilMethod != null) cilType.Methods.Add(cilMethod); } Where.Pop(); } } if (jclass.Name == "java.lang.Class") JavaLangClassExplicitCast(cilType); } } FieldDefinition BuildCilField(TypeDefinition cilType, JavaField jfield) { FieldAttributes attrs = 0; if ((jfield.Flags & JavaAccessFlags.ACC_PUBLIC) != 0) attrs = FieldAttributes.Public; else if ((jfield.Flags & JavaAccessFlags.ACC_PRIVATE) != 0) attrs = FieldAttributes.Private; else if ((jfield.Flags & JavaAccessFlags.ACC_PROTECTED) != 0) attrs = FieldAttributes.FamORAssem; else attrs = FieldAttributes.Assembly; if ((jfield.Flags & JavaAccessFlags.ACC_STATIC) != 0) attrs |= FieldAttributes.Static; if ((jfield.Flags & JavaAccessFlags.ACC_FINAL) != 0) attrs |= FieldAttributes.InitOnly; var fieldDef = new FieldDefinition( jfield.Name, attrs, CilTypeReference(jfield.Type)); if (cilType.IsInterface && jfield.Constant != null) { fieldDef.Constant = jfield.Constant; fieldDef.IsLiteral = true; } return fieldDef; } MethodDefinition BuildCilMethod(TypeDefinition cilType, JavaMethod jmethod) { MethodAttributes attrs = 0; if ((jmethod.Flags & JavaAccessFlags.ACC_PUBLIC) != 0) attrs = MethodAttributes.Public; else if ((jmethod.Flags & JavaAccessFlags.ACC_PRIVATE) != 0) attrs = MethodAttributes.Private; else if ((jmethod.Flags & JavaAccessFlags.ACC_PROTECTED) != 0) attrs = MethodAttributes.FamORAssem; else attrs = MethodAttributes.Assembly; var methodName = jmethod.Name; if (methodName == "") return null; if (! cilType.IsInterface) { var newMethodName = CilMethod.TranslateNameJvmToClr(jmethod); if (newMethodName != null) methodName = newMethodName; } if ((jmethod.Flags & JavaAccessFlags.ACC_STATIC) != 0) attrs |= MethodAttributes.Static; else if (methodName == "") { methodName = ".ctor"; attrs |= MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; } else { attrs |= MethodAttributes.Virtual; if ((jmethod.Flags & JavaAccessFlags.ACC_ABSTRACT) != 0) attrs |= MethodAttributes.Abstract; } if ((jmethod.Flags & JavaAccessFlags.ACC_FINAL) != 0) attrs |= MethodAttributes.Final; if (cilType.IsInterface) { attrs |= MethodAttributes.Abstract | MethodAttributes.NewSlot; } attrs |= MethodAttributes.HideBySig; var methodDef = new MethodDefinition( methodName, attrs, CilTypeReference(jmethod.ReturnType)); if ((jmethod.Flags & JavaAccessFlags.ACC_STATIC) == 0) methodDef.HasThis = true; foreach (var jparam in jmethod.Parameters) { var paramType = CilTypeReference(jparam.Type); var newParamDef = new ParameterDefinition(jparam.Name, 0, paramType); methodDef.Parameters.Add(newParamDef); var paramClass = jparam.Type.ClassName; if ( paramClass != null && funcIfcNames.Contains(paramClass) && paramType is TypeDefinition paramTypeDef) { // move interface from set of potentially functional interfaces // to set of such interfaces actually appearing as parameter funcIfcNames.Remove(paramClass); funcIfcTypes.Add(paramTypeDef); } } if ((jmethod.Flags & JavaAccessFlags.ACC_ABSTRACT) == 0 && (! cilType.IsInterface)) { SetCommonMethodBody(methodDef); } return methodDef; } void SetCommonMethodBody(MethodDefinition method) { if (methodBodyNotImplementedExceptionThrow == null) { var body = new MethodBody(method); body.GetILProcessor().Emit( Mono.Cecil.Cil.OpCodes.Newobj, methodRefNotImplementedException); body.GetILProcessor().Emit( Mono.Cecil.Cil.OpCodes.Throw); methodBodyNotImplementedExceptionThrow = body; } method.Body = methodBodyNotImplementedExceptionThrow; } TypeReference CilTypeReference(JavaType jtype) { string typeName = jtype.ClassName; if (typeName == null) typeName = "./" + jtype.PrimitiveType; if (typeMap.TryGetValue(typeName, out var cilType)) { CilTypeAddUse(cilType); if (jtype.ArrayRank != 0) cilType = new ArrayType(cilType, jtype.ArrayRank); return cilType; } if (typeName.IndexOf('$') == -1) { Console.WriteLine($"auto-generating empty class for '{typeName}'"); string nsName, clsName; int dot = typeName.LastIndexOf('.'); if (dot == -1 || dot + 1 == typeName.Length) { nsName = null; clsName = typeName; } else { nsName = typeName.Substring(0, dot - 1); clsName = typeName.Substring(dot + 1); } cilType = new TypeDefinition(nsName, clsName, TypeAttributes.Public); CilTypeAddUse(cilType); typeMap[typeName] = cilType; module.Types.Add(cilType as TypeDefinition); return cilType; } throw new JavaException($"unknown type '{typeName}'", Where); } void CilTypeAddUse(TypeReference cilType) { while (cilType != null) { typeUse.Add(cilType); cilType = cilType.DeclaringType; } } static bool IsPublicOrProtected(JavaAccessFlags flags) { return (flags & (JavaAccessFlags.ACC_PUBLIC | JavaAccessFlags.ACC_PROTECTED)) != 0; } static bool SkipMethodByReturnType(JavaMethod method) { // discard bridge methods that only change the return types int n = method.Parameters?.Count ?? 0; foreach (var otherMethod in method.Class.Methods) { if (otherMethod != method && otherMethod.Name == method.Name && method.Name != "") { int n2 = otherMethod.Parameters?.Count ?? 0; if (n == n2) { bool same = true; for (int i = 0; i < n && same; i++) { same = method.Parameters[i].Type.Equals(otherMethod.Parameters[i].Type); } if (same) { if ( method.ReturnType.ClassName != method.Class.Name && otherMethod.ReturnType.ClassName == method.Class.Name) { return true; } } } } } return false; } void JavaLangClassExplicitCast(TypeDefinition cilType) { MethodAttributes attrs = MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName; var method = new MethodDefinition( "op_Explicit", attrs, CilTypeReference(JavaType.ClassType)); method.Parameters.Add(new ParameterDefinition( "source", 0, new TypeReference("System", "Type", module, module.TypeSystem.CoreLibrary))); SetCommonMethodBody(method); cilType.Methods.Add(method); } }