void Translate_Throw(Mono.Cecil.Cil.Instruction inst) { if (inst.OpCode.Code == Code.Rethrow) { // if a catch clause contains a 'rethrow' instruction then // a reference to the exception object should have previously // been saved in a local (in SaveExceptionObject) CatchClause nearestClause = FindNearestCatchClause(inst.Offset); if (!nearestClause.includesRethrow) { throw new InvalidProgramException(); } code.NewInstruction(0x19 /* aload */, null, nearestClause.tryClause.localIndex); } // in the jvm, the stack trace is recorded when the exception // object is created. in the clr, the stack trace is recorded // only when the exception is thrown, so we have to emulate it. // // (also note that our system.Exception replacement (in baselib) // discards the initial call to fillInStackTrace, which occurs // during the construction of the exception object.) code.NewInstruction(0xB6 /* invokevirtual */, ThrowableType, new JavaMethodRef("fillInStackTrace", ThrowableType)); code.NewInstruction(0xBF /* athrow */, null, null); }
void ProcessOneInstruction(Mono.Cecil.Cil.Instruction cilInst) { #if DEBUGDIAG var(stack1, stack2) = stackMap.FrameToString(); Console.WriteLine($"LOCALS [{stack1}] STACK [{stack2}]"); Console.WriteLine(cilInst); #endif int instructionsCount = code.Instructions.Count; code.SetLabel((ushort)cilInst.Offset); this.cilInst = cilInst; this.cilOp = cilInst.OpCode.Code; exceptions.CheckAndSaveFrame(cilInst); var genericMark = CilMain.GenericStack.Mark(); CallInstructionTranslator(); CilMain.GenericStack.Release(genericMark); while (instructionsCount < code.Instructions.Count) { var newInst = code.Instructions[instructionsCount++]; #if DEBUGDIAG newInst.Line = (ushort)(lineNumber != 0 ? lineNumber : cilInst.Offset); #else newInst.Line = (ushort)lineNumber; #endif } }
public void Translate(Mono.Cecil.Cil.Instruction inst) { switch (inst.OpCode.Code) { case Code.Throw: case Code.Rethrow: Translate_Throw(inst); break; case Code.Leave: case Code.Leave_S: Translate_Leave(inst); break; case Code.Endfinally: Translate_Endfinally(inst.Offset); break; case Code.Endfilter: Translate_Endfilter(inst); return; default: throw new InvalidProgramException(); } // // these instructions break the normal flow of execution and clear the // stack frame. if the following instruction already has a stack frame, // due to some earlier forward jump, we load that frame // CilMain.LoadFrameOrClearStack(stackMap, inst); locals.TrackUnconditionalBranch(inst); }
public void CheckAndSaveFrame(Mono.Cecil.Cil.Instruction inst) { int offset = inst.Offset; if (!catchClauses.TryGetValue(offset, out var catchClause)) { // // on every instruction, and in particular on entry to a 'try' // block, we want to save the stack frame. // // - for the 'try' case: due to possibility of any exception // occuring at any point within the try block, exception clauses // always roll back to that stack frame at the top of the 'try'. // // - for any other instruction: because it may be the target // of a backwards jump at the end of a loop. // stackMap.SaveFrame((ushort)offset, false, CilMain.Where); } else { // // otherwise this is entry to a catch/filter/finally clause. // // if this is the first exception in the chain, we would also // need to push the exception type into the stack frame, // before saving it. // ExceptionClauseSetup((ushort)offset, catchClause); } }
void Translate_Endfilter(Mono.Cecil.Cil.Instruction inst) { // operand stack should contain an integer var integer = stackMap.PopStack(CilMain.Where); if (!integer.Equals(JavaType.IntegerType)) { throw new InvalidProgramException(); } CatchClause filterClause = null; foreach (var tryClause in tryClauses) { foreach (var catchClause in tryClause.catchClauses) { if (catchClause.filterCondStart != 0 && catchClause.filterCondStart < inst.Offset && catchClause.catchStart == inst.Next?.Offset) { filterClause = catchClause; break; } } } if (filterClause == null) { throw new InvalidProgramException(); } // make the stack contain [ Throwable, integer ]. the integer // is already on the stack as the result of the filter test code. // the Throwable was stored in a local in ExceptionClauseSetup. var localIndex = filterClause.tryClause.localIndex; code.NewInstruction(0x19 /* aload */, null, localIndex); locals.FreeTempIndex(localIndex); stackMap.PushStack(ThrowableType); code.NewInstruction(0x5F /* swap */, null, null); ExceptionClauseCommon(filterClause); }
void Translate_Leave(Mono.Cecil.Cil.Instruction inst) { var catchClause = RollbackStackFrame(inst.Offset); if (catchClause != null && catchClause.finallyClause) { // leave is only permitted in a try or catch clauses, // but not a finally or a fault clause throw new InvalidProgramException(); } if (inst.Operand is Mono.Cecil.Cil.Instruction leaveTarget) { bool directJump; var finallyClause1 = FindNearestFinallyClause(inst.Offset); if (finallyClause1 == null) { // if there is no finally clause, we can jump directly directJump = true; } else { // check for a control transfer within the same try clause, // in this case we do not need to handle the call to 'finally' var finallyClause2 = FindNearestFinallyClause(leaveTarget.Offset); directJump = (finallyClause1 == finallyClause2); } if (directJump) { if (leaveTarget != inst.Next) { code.NewInstruction(0xA7 /* goto */, null, (ushort)leaveTarget.Offset); stackMap.SaveFrame((ushort)leaveTarget.Offset, true, CilMain.Where); } else { code.NewInstruction(0x00 /* nop */, null, null); } } else { // if we have a 'finally' clause, the 'leave' target indicates // where execution should resume, after executing the 'finally' // handler. but the 'finally' handler does not specify where to // resume. // // typically, normal execution resumes just past the end of the // 'finally' handler, but there are exceptions to this rule; // for example, the 'finally' handler may be immediately followed // by a 'catch' clause from an enclosing try/catch block. // // in addition, the 'leave' target may specify some other offset, // for example if the 'catch' contains a 'return' statement. // and the try/catch block may not have any 'leave' instructions // at all, if for example, all clauses end with a 'throw'. // // to work around all these issues, we create a 'fake' exception // object that specifies the leave target. this is identified // in 'endfinally' and used to jump to the requested offset. // see Translate_Endfinally. ushort leaveOffset = (ushort)leaveTarget.Offset; foreach (var tryClause in tryClauses) { var catchClauses = tryClause.catchClauses; var lastClause = catchClauses[catchClauses.Count - 1]; if (lastClause.finallyClause) { if (lastClause.leaveOffsets == null) { lastClause.leaveOffsets = new List <ushort>(); } if (lastClause.leaveOffsets.IndexOf(leaveOffset) == -1) { lastClause.leaveOffsets.Add(leaveOffset); } } } code.NewInstruction(0x12 /* ldc */, null, (int)leaveTarget.Offset); stackMap.PushStack(JavaType.IntegerType); code.NewInstruction(0xB8 /* invokestatic */, LeaveTargetType, new JavaMethodRef("New", ThrowableType, JavaType.IntegerType)); stackMap.PopStack(CilMain.Where); stackMap.PushStack(ThrowableType); var finallyOffset = (ushort)finallyClause1.catchStart; if (finallyOffset != inst.Next?.Offset) { code.NewInstruction(0xA7 /* goto */, null, (ushort)finallyOffset); stackMap.SaveFrame((ushort)finallyOffset, true, CilMain.Where); } stackMap.PopStack(CilMain.Where); // pop null } } else { throw new InvalidProgramException(); } }
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()); } }