using System; using System.Collections.Generic; using Mono.Cecil; using SpaceFlint.JavaBinary; namespace SpaceFlint.CilToJava { public static class InterfaceBuilder { public static int CastableInterfaceCount(List allInterfaces) { // a castable interface is an interface with generic parameters, // except those that are already implemented by a base type, // because those were already counted as castable in the base type. // see also: InitInterfaceArrayField and BuildTryCastMethod. int num = 0; foreach (var ifc in allInterfaces) { if (ifc.GenericTypes != null && (! ifc.SuperImplements)) num++; } return num; } public static List BuildProxyMethods(List allInterfaces, TypeDefinition fromType, CilType intoType, JavaClass theClass) { // // process only if the class (or interface) has any methods or super interfaces // var classMethods = theClass.Methods; if (classMethods.Count == 0) return null; bool isInterface = intoType.IsInterface; if ((! isInterface) && theClass.Interfaces == null) return null; var theMethods = CilInterfaceMethod.CollectAll(fromType); // // if any interfaces are marked [RetainName], make sure that // all corresponding methods are also marked [RetainName] // CheckRetainNameMethods(theMethods, allInterfaces, intoType); // // if this is an abstract class but forced to an interface via [AddInterface] // decoration, then we need to remove all constructors generated for the class // if (intoType.IsInterface) { if (! fromType.IsInterface) { for (int i = classMethods.Count; i-- > 0; ) { if (classMethods[i].Name == "") classMethods.RemoveAt(i); } } if (intoType.HasGenericParameters) { // the RuntimeType constructor in baselib uses IGenericEntity // marker interface to identify generic classes. note that // real generic types implement IGenericObject -> IGenericEntity. theClass.AddInterface("system.IGenericEntity"); } return null; } // // for each implemented interface, build proxy methods // List output = null; int ifcNumber = 0; foreach (var ifc in allInterfaces) { if ((! ifc.DirectReference) && ifc.SuperImplements) { // we don't have to build proxy for an interface if it is // implemented by a super type and not by our primary type continue; } if (ifc.GenericTypes == null) { foreach (var ifcMethod in ifc.Methods) { // build proxy methods: interface$method -> method var newMethod = BuildPlainProxy(ifcMethod, intoType, theMethods); if (newMethod != null) { newMethod.Class = theClass; theClass.Methods.Add(newMethod); } } } else { var ifcClass = CreateInnerClass(theClass, intoType, ++ifcNumber); ifcClass.AddInterface(ifc.InterfaceType.JavaName); if (output == null) { output = new List(); CreateInterfaceArrayField(theClass); } output.Add(ifcClass); // if the class implements a generic interface for multiple types, // then we need a method suffix to differentiate between the methods. // see also: CilMethod::InsertMethodNamePrefix string methodSuffix = ""; foreach (var genericType in ifc.GenericTypes) methodSuffix += "--" + CilMethod.GenericParameterSuffixName(genericType); foreach (var ifcMethod in ifc.Methods) { // build proxy classes: proxy sub-class -> this class BuildGenericProxy(ifcMethod, methodSuffix, intoType, theMethods, ifcClass); } } } return output; JavaClass CreateInnerClass(JavaClass parentClass, CilType parentType, int ifcNumber) { // generic interfaces are implemented as proxy sub-classes which // call methods on the parent class object. we need to define // an inner class. this class has one instance field which is a // reference to the parent class. the constructor takes this // reference as a parameter and initializes the instance field. var newClass = CilMain.CreateInnerClass(parentClass, parentClass.Name + "$$generic" + ifcNumber.ToString()); var fld = new JavaField(); fld.Name = ParentFieldName; fld.Type = parentType; fld.Class = newClass; fld.Flags = JavaAccessFlags.ACC_PRIVATE; newClass.Fields.Add(fld); var code = CilMain.CreateHelperMethod(newClass, new JavaMethodRef("", JavaType.VoidType, JavaType.ObjectType), 2, 2); code.Method.Flags &= ~JavaAccessFlags.ACC_BRIDGE; // invalid for constructor code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0xB7 /* invokespecial */, JavaType.ObjectType, new JavaMethodRef("", JavaType.VoidType)); code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0x19 /* aload */, null, (int) 1); code.NewInstruction(0xC0 /* checkcast */, parentType, null); code.NewInstruction(0xB5 /* putfield */, new JavaType(0, 0, newClass.Name), new JavaFieldRef(ParentFieldName, parentType)); code.NewInstruction(JavaType.VoidType.ReturnOpcode, null, null); return newClass; } void CreateInterfaceArrayField(JavaClass parentClass) { // the parent class has a helper array field that is used to track // the proxy objects generated for implemented generic interfaces. // see also: InitInterfaceArrayField, below. var fld = new JavaField(); fld.Name = InterfaceArrayField.Name; fld.Type = InterfaceArrayField.Type; fld.Class = parentClass; fld.Flags = JavaAccessFlags.ACC_PRIVATE; if (parentClass.Fields == null) parentClass.Fields = new List(1); parentClass.Fields.Add(fld); } } // // InitInterfaceArrayField // public static void InitInterfaceArrayField(CilType toType, int numCastableInterfaces, JavaCode code) { // if a type has castable interface (as counted by CastableInterfaceCount), // then we need to initialize the helper array field, for use by the // implementation of the IGenericObject.TryCast helper method if (numCastableInterfaces == 0) return; code.NewInstruction(0x19 /* aload */, null, (int) 0); code.StackMap.PushStack(toType); code.NewInstruction(0xBB /* new */, AtomicReferenceArrayType, null); code.StackMap.PushStack(AtomicReferenceArrayType); code.NewInstruction(0x59 /* dup */, null, null); code.StackMap.PushStack(AtomicReferenceArrayType); code.NewInstruction(0x12 /* ldc */, null, numCastableInterfaces); code.StackMap.PushStack(JavaType.IntegerType); code.NewInstruction(0xB7 /* invokespecial */, AtomicReferenceArrayType, new JavaMethodRef("", JavaType.VoidType, JavaType.IntegerType)); code.NewInstruction(0xB5 /* putfield */, toType, InterfaceArrayField); code.StackMap.PopStack(CilMain.Where); code.StackMap.PopStack(CilMain.Where); code.StackMap.PopStack(CilMain.Where); code.StackMap.PopStack(CilMain.Where); } // // if any interfaces are marked [RetainName], make sure that // all corresponding methods are also marked [RetainName] // static void CheckRetainNameMethods(List theMethods, List theInterfaces, CilType checkedType) { List retainNameInterfaces = null; foreach (var myInterface in theInterfaces) { if (myInterface.InterfaceType.IsRetainName) { if (retainNameInterfaces == null) retainNameInterfaces = new List(); retainNameInterfaces.Add(myInterface); } } if (retainNameInterfaces == null) return; foreach (var myMethod in theMethods) { // if the method was not declared on the type itself, skip it. if (myMethod.Method.DeclType != checkedType) continue; // methods may be marked with [RetainName] to avoid shadow renaming // (via CilMethod::MethodIsShadowing), as well as to match methods // from [RetainName] interfaces. so if the method is marked so, // then we can just skip it. if (myMethod.Method.IsRetainName) continue; // if the method is an explicit method implementation, then it cannot // implement a method from a [RetainName] interface (checked in // CilMethod::InsertMethodNamePrefix) and cannot be marked [RetainName] // (checked in CilMethod::SetMethodType). if (myMethod.Method.IsExplicitImpl) continue; // if the method is not marked [RetainName], then make sure it is not // overriding an interface method marked [RetainName] var (foundInterface, foundMethod) = FindMethod(retainNameInterfaces, myMethod); if (foundMethod != null) { var interfaceName = foundInterface.InterfaceType.JavaName; throw CilMain.Where.Exception( $"method '{myMethod}' (for interface '{interfaceName}') " + $"should be decorated with [java.attr.RetainName]"); } } (CilInterface, CilInterfaceMethod) FindMethod(List haystack, CilInterfaceMethod needle) { foreach (var ifc in haystack) { var mth = CilInterfaceMethod.FindMethod(ifc.Methods, needle); if (mth != null) return (ifc, mth); } return (null, null); } } public static JavaMethod BuildPlainProxy(CilInterfaceMethod ifcMethod, CilType intoType, List classMethods) { CilMethod targetMethod = null; foreach (var clsMethod in classMethods) { if (clsMethod.Method.IsExplicitImpl) { // no need for a proxy if we already have an override method, // which has the same name as the proxy: interface$method if (ifcMethod.Method.Name == clsMethod.Method.Name) return null; } else if (ifcMethod.PlainCompare(clsMethod)) { // more than one method may match, if a derived type overrides // or hides a method that also exists in a base type. but the // derived (primary) type methods always come first. if (targetMethod == null) targetMethod = clsMethod.Method; } } if (targetMethod == null) { throw CilMain.Where.Exception( $"missing method '{ifcMethod.Method}' " + $"(for interface '{ifcMethod.Method.DeclType}')"); } if (targetMethod.IsRetainName) { // method retains is name, so it doesn't require a proxy bridge return null; } // // create proxy method // var newMethod = new JavaMethod(null, targetMethod); newMethod.Name = ifcMethod.Method.Name; newMethod.Flags = JavaAccessFlags.ACC_PUBLIC | JavaAccessFlags.ACC_BRIDGE; var code = newMethod.Code = new JavaCode(); code.Method = newMethod; code.Instructions = new List(); // // push 'this' and all other parameters // code.NewInstruction(0x19 /* aload */, null, (int) 0); int numArgs = newMethod.Parameters.Count; int index = 1; for (int i = 0; i < numArgs; i++) { var arg = targetMethod.Parameters[i].Type; code.NewInstruction(arg.LoadOpcode, null, (int) index); index += arg.Category; } // // invoke proxy target method and return // code.NewInstruction(0xB6 /* invokevirtual */, intoType, targetMethod); code.NewInstruction(targetMethod.ReturnType.ReturnOpcode, null, null); code.MaxLocals = code.MaxStack = index; return newMethod; } public static void BuildGenericProxy(CilInterfaceMethod ifcMethod, string methodSuffix, CilType intoType, List classMethods, JavaClass ifcClass) { CilMethod targetMethod = null; //Console.WriteLine("\n***** LOOKING FOR INTERFACE METHOD " + ifcMethod + " (SUFFIX " + methodSuffix + ") AKA " + ifcMethod.Method); foreach (var clsMethod in classMethods) { /*Console.WriteLine("> LOOKING AT " + (clsMethod.Method.IsExplicitImpl ? "EXPLICIT " : "") + "CLASS METHOD " + clsMethod + " AKA " + clsMethod.Method);*/ if (ifcMethod.GenericCompare(clsMethod)) { if (clsMethod.Method.IsExplicitImpl) { // a matching explicit override method is the best match, // and we can immediately stop searching targetMethod = clsMethod.Method; break; } else { // more than one method may match, if a derived type overrides // or hides a method that also exists in a base type. but the // derived (primary) type methods always come first. if (targetMethod == null) targetMethod = clsMethod.Method; } } } if (targetMethod == null) { throw CilMain.Where.Exception( $"missing method '{ifcMethod.Method}' " + $"(for interface '{ifcMethod.Method.DeclType}')"); } // Console.WriteLine("INTERFACE METHOD " + ifcMethod.Method + " TARGET " + targetMethod); BuildGenericProxy2(ifcMethod, targetMethod, true, intoType, ifcClass); } public static void BuildGenericProxy2(CilInterfaceMethod ifcMethod, CilMethod targetMethod, bool parentField, CilType intoType, JavaClass ifcClass) { // // create proxy method // var targetMethod2 = targetMethod.WithGenericParameters; var ifcMethod2 = ifcMethod.Method.WithGenericParameters; var newMethod = new JavaMethod(ifcClass, targetMethod2); newMethod.Name = ifcMethod2.Name; newMethod.Flags = JavaAccessFlags.ACC_PUBLIC | JavaAccessFlags.ACC_BRIDGE; var code = newMethod.Code = new JavaCode(); code.Method = newMethod; code.Instructions = new List(); // // push a reference to the parent object // code.NewInstruction(0x19 /* aload */, null, (int) 0); if (parentField) { code.NewInstruction(0xB4 /* getfield */, new JavaType(0, 0, ifcClass.Name), new JavaFieldRef(ParentFieldName, intoType)); } // // push all other parameters // int numArgs = newMethod.Parameters.Count; int index = 1; int maxStack = 1; for (int i = 0; i < numArgs; i++) { var ifcArg = ifcMethod2.Parameters[i].Type; code.NewInstruction(ifcArg.LoadOpcode, null, (int) index); index += ifcArg.Category; var clsArg = (CilType) targetMethod2.Parameters[i].Type; if (JavaType.ObjectType.Equals(ifcArg)) { if (! clsArg.IsReference) { var boxedArg = new BoxedType(clsArg, false); code.NewInstruction(0xC0 /* checkcast */, boxedArg, null); boxedArg.GetValue(code); } else if (! JavaType.ObjectType.Equals(clsArg)) { code.NewInstruction(0xC0 /* checkcast */, clsArg, null); } // a parameter in the target method may be a concrete type, // but if it is a generic java.lang.Object in the interface, // then it must be a generic java.lang.Object in the proxy newMethod.Parameters[i] = new JavaFieldRef("", ifcArg); } maxStack += clsArg.Category; } // // invoke proxy target method // code.NewInstruction(0xB6 /* invokevirtual */, intoType, targetMethod2); // // return value from method // var clsRet = (CilType) targetMethod2.ReturnType; var ifcRet = ifcMethod2.ReturnType; if (JavaType.ObjectType.Equals(ifcRet)) { if (! clsRet.IsReference) { var boxedArg = new BoxedType(clsRet, false); boxedArg.BoxValue(code); } // the return value in the target method may be a concrete type, // but if it is a generic java.lang.Object in the interface, // then it must also be a generic java.lang.Object in the proxy newMethod.ReturnType = ifcRet; code.NewInstruction(ifcRet.ReturnOpcode, null, null); } else { code.NewInstruction(clsRet.ReturnOpcode, null, null); } code.MaxLocals = index; code.MaxStack = maxStack; ifcClass.Methods.Add(newMethod); } public static void BuildOverloadProxy(TypeDefinition fromType, MethodDefinition fromMethod, CilMethod targetMethod, JavaClass intoClass) { // create a proxy bridge to forward invocations of a virtual method from // the base class, to an implementation in a derived class. this is needed // where the base virtual method has generic parameters or return type, // for example: virtual T SomeMethod(T arg) will be translated as: // java.lang.Object SomeMethod(-generic-$)(java.lang.Object arg) // and in a derived class that specializes T as String: // java.lang.String SomeMethod(java.lang.String arg) // so the derived class must also include a proxy bridge method with the // same name as in the base class, and forward the call. //Console.WriteLine($"CHECK OVERRIDE {fromMethod}"); int targetMethodCount = targetMethod.Parameters.Count; for (;;) { var baseType = fromType.BaseType; if (baseType == null) break; fromType = CilType.AsDefinition(baseType); if (! baseType.IsGenericInstance) continue; foreach (var fromMethod2 in fromType.Methods) { //Console.WriteLine($"\tCOMPARE {fromMethod2} {fromMethod2.IsVirtual} {fromMethod2.Name == fromMethod.Name} {targetMethodCount == fromMethod2.Parameters.Count}"); if ( fromMethod2.IsVirtual && fromMethod2.Name == fromMethod.Name && targetMethodCount == fromMethod2.Parameters.Count) { if (CilMethod.CompareMethods(fromMethod, fromMethod2)) { //Console.WriteLine(">>>>>>>>>>>>> WARNING SAME: " + fromMethod.ToString() + " AND " + fromType); continue; } var genericMark = CilMain.GenericStack.Mark(); CilMain.GenericStack.EnterType(baseType); var baseMethod = new CilInterfaceMethod( CilMain.GenericStack.EnterMethod(fromMethod2)); CilMain.GenericStack.Release(genericMark); //Console.WriteLine($"\twould compare with {fromMethod2} in class {baseType}"); //Console.WriteLine($"\t\t{baseMethod}"); if (targetMethodCount != baseMethod.Parameters.Count) continue; if (! IsGenericOrEqual(targetMethod.ReturnType, baseMethod.ReturnType)) continue; bool sameParameters = true; for (int i = 0; i < targetMethodCount; i++) { if (! IsGenericOrEqual(targetMethod.Parameters[i].Type, baseMethod.Parameters[i])) { bool equalsAfterUnboxing = ( targetMethod.Parameters[i].Type is BoxedType boxedType && boxedType.UnboxedType.Equals(baseMethod.Parameters[i])); if (! equalsAfterUnboxing) { //Console.WriteLine($"\tMISMATCH {targetMethod.Parameters[i].Type} vs {baseMethod.Parameters[i]}/{baseMethod.Parameters[i].IsGenericParameter}"); sameParameters = false; break; } } } if (sameParameters) { //Console.WriteLine($"proxying {targetMethod} in class {targetMethod.DeclType}"); BuildGenericProxy2(baseMethod, targetMethod, false, targetMethod.DeclType, intoClass); return; } } } } static bool IsGenericOrEqual(JavaType pThis, CilType pBase) => pBase.IsGenericParameter || pThis.Equals(pBase); } // // implement the method IGenericObject.TryCast(Type) // public static void BuildTryCastMethod(List allInterfaces, CilType intoType, int numCastableInterfaces, JavaClass intoClass) { var code = CilMain.CreateHelperMethod(intoClass, new JavaMethodRef("system-IGenericObject-TryCast", JavaType.ObjectType, CilType.SystemTypeType), 3, 0); code.StackMap = new JavaStackMap(); code.StackMap.SetLocal(0, intoType); code.StackMap.SetLocal(1, CilType.SystemTypeType); code.StackMap.SaveFrame(0, false, CilMain.Where); if (intoType.HasGenericParameters) { // if this type is a generic type, compare it to the input. // see also: built GenericUtil::BuildGetTypeMethod() code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0xB4 /* getfield */, intoType, GenericUtil.ConcreteTypeField); code.NewInstruction(0x19 /* aload */, null, (int) 1); code.NewInstruction(0xA5 /* if_acmpeq */, null, (ushort) 0xFFF1); // branch to label 0xFFF1 to return 'this' reference if (code.MaxStack < 2) code.MaxStack = 2; } if (intoType.SuperTypes[0].IsGenericThisOrSuper) { // if the base type has IGenericObject::TryCast, call that. code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0x19 /* aload */, null, (int) 1); code.NewInstruction(0xB7 /* invokespecial */, intoType.SuperTypes[0], code.Method); code.NewInstruction(0x59 /* dup */, null, null); code.NewInstruction(0xC7 /* ifnonnull */, null, (ushort) 0xFFF2); // branch to label 0xFFF2 to return result from super TryCast code.NewInstruction(0x57 /* pop */, null, null); if (code.MaxStack < 2) code.MaxStack = 2; } if (numCastableInterfaces != 0) { // compare the input type to any implemented generic interface, // unless that interface is also implemented by a super type. int ifcNumber = 0; foreach (var ifc in allInterfaces) { if (ifc.GenericTypes != null && (! ifc.SuperImplements)) { // this is a generic interface, not implemented by a super. if (ifcNumber == 0) { // before processing the first such interface, make an // easy to access reference to the array of interface // types (see also CreateInterfaceArrayField). code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0xB4 /* getfield */, intoType, InterfaceArrayField); code.NewInstruction(0x3A /* astore */, null, (int) 2); } // set up parameters to call the GenericType::TryCast helper code.NewInstruction(0x19 /* aload */, null, (int) 0); code.NewInstruction(0x19 /* aload */, null, (int) 1); code.NewInstruction(0x19 /* aload */, null, (int) 2); code.NewInstruction(0x12 /* ldc */, null, (int) ifcNumber++); var proxyClassName = intoType.JavaName + "$$generic" + ifcNumber.ToString(); code.NewInstruction(0x12 /* ldc */, new JavaType(0, 0, proxyClassName), null); foreach (var inst in ifc.LoadTypeCode.Instructions) code.Instructions.Add(inst); code.NewInstruction(0xB8 /* invokestatic */, GenericUtil.SystemGenericType, TryCastHelperMethod); code.NewInstruction(0x59 /* dup */, null, null); code.NewInstruction(0xC7 /* ifnonnull */, null, (ushort) 0xFFF2); code.NewInstruction(0x57 /* pop */, null, null); // branch to label 0xFFF2 to return proxy object reference int minStack = ifc.LoadTypeCode.MaxStack + 5; if (code.MaxStack < minStack) code.MaxStack = minStack; } } } // exit points. if control reaches this point, we return null, because // no matching class or interface type was found. otherwise if there was // a branch to label 0xFFF1, then we need to return the 'this' reference. // and finally, a branch to label 0xFFF2, with a reference on the stack. // label 0xFFF0 - return null code.StackMap.SaveFrame(0xFFF0, true, CilMain.Where); code.NewInstruction(0x01 /* aconst_null */, null, null, 0xFFF0); code.NewInstruction(JavaType.ObjectType.ReturnOpcode, null, null); // label 0xFFF1 - return 'this' reference code.StackMap.SaveFrame(0xFFF1, true, CilMain.Where); code.NewInstruction(0x19 /* aload */, null, (int) 0, 0xFFF1); // label 0xFFF2 - return reference on the stack code.StackMap.PushStack(JavaType.ObjectType); code.StackMap.SaveFrame(0xFFF2, true, CilMain.Where); code.NewInstruction(JavaType.ObjectType.ReturnOpcode, null, null, 0xFFF2); } // // static variables // internal static readonly JavaType AtomicReferenceArrayType = new JavaType(0, 0, "java.util.concurrent.atomic.AtomicReferenceArray"); internal static readonly JavaFieldRef InterfaceArrayField = new JavaFieldRef("-generic-interfaces", AtomicReferenceArrayType); internal static readonly string ParentFieldName = "-generic-parent"; internal static readonly JavaMethodRef TryCastHelperMethod; static InterfaceBuilder() { var parameters = new List(6); parameters.Add(new JavaFieldRef("", JavaType.ObjectType)); parameters.Add(new JavaFieldRef("", CilType.SystemTypeType)); parameters.Add(new JavaFieldRef("", AtomicReferenceArrayType)); parameters.Add(new JavaFieldRef("", JavaType.IntegerType)); parameters.Add(new JavaFieldRef("", JavaType.ClassType)); parameters.Add(new JavaFieldRef("", CilType.SystemTypeType)); TryCastHelperMethod = new JavaMethodRef("TryCast", JavaType.ObjectType, parameters); } } }