예제 #1
0
        public static void LoadFunction(JavaCode code, Mono.Cecil.Cil.Instruction cilInst)
        {
            if (cilInst.Operand is MethodReference implMethodRef)
            {
                var implMethod = CilMethod.From(implMethodRef);

                var(declType, declMethod, dlgMethodName) = FindInterfaceType(cilInst);

                if (dlgMethodName != null)
                {
                    // if this is an artificial delegate for a functional interface,
                    // then we can't actually instantiate this particular delegate,
                    // because its definition only exists in the DLL created by
                    // DotNetImporter;  see BuildDelegate there.  we fix the Newobj
                    // instruction to instantiate system.FunctionalInterfaceDelegate.

                    cilInst.Next.Operand = DelegateConstructor(cilInst.Next.Operand);

                    declMethod = new JavaMethodRef(dlgMethodName,
                                                   implMethod.ReturnType,
                                                   implMethod.Parameters);
                }

                if (IsPrimitive(declMethod.ReturnType))
                {
                    // primitive return value is translated to java.lang.Object,
                    // because the delegate target may return a generic type that
                    // is specialized for the primitive type in the delegate.
                    // see also MakeInterface and GenerateInvoke
                    declMethod = new JavaMethodRef(
                        declMethod.Name, JavaType.ObjectType, declMethod.Parameters);
                }

                // select the method handle kind for the dynamic call site.
                // for a non-static invocation, we need to push an object reference.

                JavaMethodHandle.HandleKind callKind;
                if (implMethod.DeclType.IsInterface)
                {
                    callKind = JavaMethodHandle.HandleKind.InvokeInterface;
                }
                else
                {
                    callKind = JavaMethodHandle.HandleKind.InvokeVirtual;
                }

                if (cilInst.OpCode.Code == Code.Ldftn)
                {
                    if (implMethod.IsStatic)
                    {
                        callKind = JavaMethodHandle.HandleKind.InvokeStatic;
                    }

                    else
                    {
                        var  implMethodDef = CilMethod.AsDefinition(implMethodRef);
                        bool isVirtual     = implMethodDef.IsVirtual;
                        if (isVirtual && implMethodDef.DeclaringType.IsSealed)
                        {
                            isVirtual = false;
                        }

                        if (!isVirtual)
                        {
                            // non-virtual instance method
                            code.NewInstruction(0x59 /* dup */, null, null);
                            code.StackMap.PushStack(JavaType.ObjectType);
                        }
                        else
                        {
                            // virtual method should only be specified in 'ldvirtftn'
                            throw new Exception("virtual method referenced");
                        }
                    }
                }

                //
                // the method may take generic type parameters, i.e. the System.Type
                // parameters that are added at the end of the parameter list, by
                // CilMethod::ImportGenericParameters.  but when the delegate is
                // called, these parameters will not be pushed.  therefore we do
                // the following when assigning such a method to a delegate:
                //
                // (1) we select a different implementing method, i.e. the bridge
                // method generated by CreateCapturingBridgeMethod (see below),
                // which moves the type arguments to the head of the parameter list.
                // (2) we generate and push the type arguments at this time, via
                // calls to GenericUtil.LoadGeneric.
                // (3) we specify the type arguments as capture parameters of the
                // call site.  (see also JavaCallSite.)  this means the generated
                // proxy will inject these parameters when it calls the bridge
                // method from step (1), and that bridge method will push these
                // parameters at the end of the parameter list, when it invokes
                // the actual target method.
                //

                List <JavaFieldRef> TypeArgs    = null;
                JavaMethodRef       implMethod2 = implMethod;

                var implGenericMethod = implMethod.WithGenericParameters;
                if (implGenericMethod != implMethod)
                {
                    implMethod2 = new JavaMethodRef(
                        "delegate-bridge-" + implGenericMethod.Name,
                        implMethod.ReturnType, implMethod.Parameters);

                    var count1 = implMethod.Parameters.Count;
                    var count2 = implGenericMethod.Parameters.Count;
                    TypeArgs = implGenericMethod.Parameters.GetRange(count1, count2 - count1);

                    var callMethod = CilMain.GenericStack.EnterMethod(implMethodRef);
                    for (int i = count1; i < count2; i++)
                    {
                        GenericUtil.LoadGeneric(implGenericMethod.Parameters[i].Name, code);
                    }
                    for (int i = count1; i < count2; i++)
                    {
                        code.StackMap.PopStack(CilMain.Where);
                    }
                }

                // create a CallSite that implements the method signature
                // declMethod, in the functional interface declType, in order
                // to proxy-invoke the method implMethod.DeclType::implMethod.

                var callSite = new JavaCallSite(declType, declMethod,
                                                implMethod.DeclType, implMethod2,
                                                TypeArgs, callKind);

                code.NewInstruction(0xBA /* invokedynamic */, null, callSite);

                if (callKind != JavaMethodHandle.HandleKind.InvokeStatic)
                {
                    code.StackMap.PopStack(CilMain.Where);
                }

                code.StackMap.PushStack(CilType.From(declType));
            }
            else
            {
                throw new InvalidProgramException();
            }

            //
            // ldftn or ldvirtftn should be followed by newobj, which we can use
            // to identify the delegate, and therefore, the interface
            //

            (JavaType, JavaMethodRef, string) FindInterfaceType(
                Mono.Cecil.Cil.Instruction cilInst)
            {
                cilInst = cilInst.Next;
                if (cilInst != null && cilInst.OpCode.Code == Code.Newobj &&
                    cilInst.Operand is MethodReference constructorRef)
                {
                    var dlgType = CilType.From(constructorRef.DeclaringType);
                    if (dlgType.IsDelegate)
                    {
                        //
                        // check for an artificial delegate, generated to represent a
                        // java functional interface: (BuildDelegate in DotNetImporter)
                        //
                        // delegate type is marked [java.lang.attr.AsInterface],
                        // and is child of an interface type.
                        // interface type is marked [java.lang.attr.RetainName],
                        // and has one method.
                        //

                        var dlgType0 = CilType.AsDefinition(constructorRef.DeclaringType);
                        if (dlgType0.HasCustomAttribute("AsInterface"))
                        {
                            var ifcType0 = dlgType0.DeclaringType;
                            var ifcType  = CilType.From(ifcType0);
                            if (ifcType.IsInterface && ifcType.IsRetainName &&
                                ifcType0.HasMethods && ifcType0.Methods.Count == 1)
                            {
                                return(ifcType, null, ifcType0.Methods[0].Name);
                            }
                        }

                        //
                        // otherwise, a normal delegate, which may be generic, or plain.
                        // interface name is DelegateType$interface.
                        // we look for a method named Invoke.
                        //

                        foreach (var method in dlgType0.Methods)
                        {
                            if (method.Name == "Invoke")
                            {
                                return(InterfaceType(dlgType), CilMethod.From(method), null);
                            }
                        }
                    }
                }
                throw new InvalidProgramException();
            }

            //
            // create a method reference to delegate constructor from baselib:
            // system.MulticastDelegate::.ctor(object, object)
            //

            CilMethod DelegateConstructor(object Operand)
            {
                var baseType = CilType.AsDefinition(((MethodReference)Operand)
                                                    .DeclaringType).BaseType;

                if (baseType.Namespace != "System" || baseType.Name != "MulticastDelegate")
                {
                    throw CilMain.Where.Exception(
                              $"delegate base type is '{baseType.Name}', "
                              + "but expected 'System.MulticastDelegate'");
                }

                return(CilMethod.CreateDelegateConstructor());
            }
        }