void InitLocalsRefs2(CilType plainType, BoxedType boxedType, bool arg, int index) { if (arg) { code.NewInstruction(plainType.LoadOpcode, null, index); } else { code.NewInstruction(plainType.InitOpcode, null, null); } stackMap.PushStack(plainType); if (boxedType != null) { boxedType.BoxValue(code); } else if (plainType.IsGenericParameter) { GenericUtil.ValueClone(code); } else { CilMethod.ValueMethod(CilMethod.ValueClone, code); code.NewInstruction(0xC0 /* checkcast */, plainType, null); } code.NewInstruction(0x3A /* astore */, null, index); stackMap.PopStack(CilMain.Where); }
public static string GenericParameterFullName(GenericParameter genericParameter) { string name = genericParameter.Name; string prefix; if (genericParameter.Type == GenericParameterType.Method) { prefix = "M!" + genericParameter.DeclaringMethod.FullName; if (name.StartsWith("!!")) { name = CilMethod.AsDefinition(genericParameter.DeclaringMethod) .GenericParameters[genericParameter.Position].Name; } else if (name.StartsWith("!")) { throw new ArgumentException(); } } else { prefix = "T!" + genericParameter.DeclaringType.FullName; if (name.StartsWith("!")) { name = CilType.AsDefinition(genericParameter.DeclaringType) .GenericParameters[genericParameter.Position].Name; } } return(prefix + "<" + name + ">"); }
public CodeLocals(CilMethod myMethod, MethodDefinition defMethod, JavaCode code) { this.code = code; stackMap = code.StackMap = new JavaStackMap(); InitLocals(myMethod, defMethod); WriteDebugData(myMethod.DeclType); }
internal static void BuildJavaCode(JavaMethod newMethod, CilMethod myMethod, MethodDefinition defMethod, int numCastableInterfaces) { var body = defMethod.Body; if (body.Instructions.Count == 0) { throw CilMain.Where.Exception("input method is empty"); } if (body.Instructions[body.Instructions.Count - 1].Offset > 0xFFF0) { throw CilMain.Where.Exception("input method is too large"); } CodeBuilder o = null; try { o = new CodeBuilder(); o.defMethod = defMethod; o.defMethodBody = defMethod.Body; o.newMethod = newMethod; o.method = myMethod; o.numCastableInterfaces = numCastableInterfaces; o.Process(); } catch (Exception e) { if (e is JavaException) { throw; } #if DEBUGDIAG Console.WriteLine(e); #endif if (o != null && o.cilInst != null) { if (o.lineNumber != 0) { CilMain.Where.Push($"line {o.lineNumber}"); } CilMain.Where.Push( $"'{o.cilInst.OpCode.Name}' instruction at offset {o.cilInst.Offset,0:X4}"); } string msg = e.Message; if (e is InvalidProgramException) { msg = "unexpected opcode or operands"; } throw CilMain.Where.Exception(msg); } }
// // create default parameterless constructor. (it will be called at the // top of any method which allocates a value type local.) and note that // if this is a generic value type, this new constructor will be further // modified in GenericUtil.MakeGenericClass.FixConstructorsInFrom // static void CreateDefaultConstructor(JavaClass valueClass, CilType fromType, int numCastableInterfaces, bool initFields) { foreach (var oldMethod in valueClass.Methods) { if (oldMethod.Name == "<init>") { return; } } var code = CilMethod.CreateConstructor(valueClass, fromType.GenericParametersCount, true); if (fromType.HasGenericParameters) { code.StackMap = new JavaStackMap(); var genericMark = CilMain.GenericStack.Mark(); CilMain.GenericStack.EnterMethod(fromType, code.Method, true); // initialize the generic type field GenericUtil.InitializeTypeField(fromType, code); CilMain.GenericStack.Release(genericMark); code.MaxStack = code.StackMap.GetMaxStackSize(CilMain.Where); } // init the array of generic interfaces InterfaceBuilder.InitInterfaceArrayField( fromType, numCastableInterfaces, code, 0); if (initFields) { var oldLabel = code.SetLabel(0xFFFF); InitializeInstanceFields(valueClass, fromType, null, code); code.SetLabel(oldLabel); } code.NewInstruction(0x19 /* aload */, null, (int)0); code.NewInstruction(0xB7 /* invokespecial */, new JavaType(0, 0, valueClass.Super), new JavaMethodRef("<init>", JavaType.VoidType)); code.MaxStack = code.StackMap.GetMaxStackSize(CilMain.Where); if (code.MaxStack < 1) { code.MaxStack = 1; } code.NewInstruction(JavaType.VoidType.ReturnOpcode, null, null); }
public static void BuildGenericProxy(CilInterfaceMethod ifcMethod, string methodSuffix, CilType intoType, List <CilInterfaceMethod> 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 ValueCopy(CilType valueType, JavaCode code, bool swap = false) { // if 'from' value is pushed before 'into' object, call with swap == false // if 'into' object is pushed before 'from' value, call with swap == true if (valueType.IsGenericParameter) { if (swap) { code.NewInstruction(0x5F /* swap */, null, null); } else { // if storing a primitive value into a generic type, // and the generic type can be resolved to a primitive type, // then use a boxed-set method call var stackArray = code.StackMap.StackArray(); int stackArrayLen = stackArray.Length; if (stackArrayLen > 0 && (!stackArray[stackArrayLen - 1].IsReference)) { var genericMark = CilMain.GenericStack.Mark(); var(primitiveType, _) = CilMain.GenericStack.Resolve(valueType.JavaName); CilMain.GenericStack.Release(genericMark); if (!primitiveType.IsReference) { var boxedType = new BoxedType(primitiveType, false); code.NewInstruction(0xC0 /* checkcast */, boxedType, null); boxedType.SetValueVO(code); return; } } } code.NewInstruction(0xB8 /* invokestatic */, SystemGenericType, new JavaMethod("Copy", JavaType.VoidType, JavaType.ObjectType, JavaType.ObjectType)); } else { if (swap) { code.NewInstruction(0x5F /* swap */, null, null); } CilMethod.ValueMethod(CilMethod.ValueCopyTo, code); } }
public void Call(CilMethod method, Mono.Cecil.Cil.Instruction inst) { int numDims = method.Parameters.Count; var elemType = method.DeclType; if (method.Name == "Get") { Deref(numDims); stackMap.PopStack(CilMain.Where); // pop index stackMap.PopStack(CilMain.Where); // pop array Load(null, elemType, inst); } else if (method.Name == "Set") { numDims--; // last parameter is value if (numDims > 1) { var valueType = stackMap.PopStack(CilMain.Where); int localIndex = locals.GetTempIndex(valueType); code.NewInstruction(valueType.StoreOpcode, null, localIndex); Deref(numDims); code.NewInstruction(valueType.LoadOpcode, null, localIndex); stackMap.PushStack(valueType); } Store(elemType); } else if (method.Name == "Address") { Deref(numDims); Address(elemType); } else { throw new InvalidProgramException(); } }
bool Translate_Constrained(CilMethod callMethod, object data) { var typeRef = data as TypeReference; if (typeRef == null) { throw new InvalidProgramException(); } var constrainType = CilType.From(typeRef); int localIndex = SaveMethodArguments(callMethod); var calledObjectType = (CilType)stackMap.PopStack(CilMain.Where); if (calledObjectType is BoxedType boxedType && boxedType.IsBoxedReference) { boxedType.GetValue(code); }
bool Translate_Callvirt(CilMethod callMethod) { var callClass = callMethod.DeclType; CheckAndBoxArguments(callMethod, true); byte op; if (callClass.IsInterface) { if (callClass.ClassName == "system.ValueMethod") { // convert calls on interface method from system.ValueMethod // to calls on virtual method from system.ValueType op = 0xB6; // invokevirtual callClass = CilType.From(CilType.SystemValueType); } else { op = 0xB9; // invokeinterface } } else { if (ConvertVirtualToStaticCall(callClass, callMethod)) { return(true); } op = 0xB6; // invokevirtual } PushGenericArguments(callMethod); code.NewInstruction(op, callClass.AsWritableClass, callMethod.WithGenericParameters); ClearMethodArguments(callMethod, false); PushMethodReturnType(callMethod); return(true); }
void Load(CilType arrayType, CilType elemType, Mono.Cecil.Cil.Instruction inst) { if (arrayType == null) { arrayType = elemType.AdjustRank(1); } /*Console.WriteLine("(LOAD) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType) || elemType.IsGenericParameter || arrayType.IsGenericParameter) { code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, LoadArrayMethod); if (elemType.ArrayRank != 0) { elemType = GenericArrayType; } } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // ldelem.i2 with a char[] array, should be 'caload' not 'saload' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } code.NewInstruction(elemType.LoadArrayOpcode, null, null); if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); } } stackMap.PushStack(elemType); }
public static CilMethod From(MethodReference fromMethod) { if (!_Methods.TryGetValue(fromMethod, out var converted)) { CilMain.Where.Push($"method '{fromMethod.FullName}'"); MethodDefinition defMethod; bool isGenericInstance = fromMethod.IsGenericInstance || fromMethod.DeclaringType.IsGenericInstance; if (fromMethod.DeclaringType is ArrayType fromArrayType) { defMethod = null; converted = new CilMethod(fromMethod, fromArrayType); if (!isGenericInstance) { _Methods.Add(fromMethod, converted); } } else { defMethod = AsDefinition(fromMethod); converted = new CilMethod(fromMethod, defMethod); if (!isGenericInstance) { _Methods.Add(fromMethod, converted); if (defMethod != fromMethod && defMethod != null && (!_Methods.TryGetValue(defMethod, out var converted2))) { _Methods.Add(defMethod, converted); } } } CilMain.Where.Pop(); } return(converted); }
void InitLocals(CilMethod myMethod, MethodDefinition defMethod) { var _localTypes = new List <CilType>(); int nextIndex = InitLocalsArgs(myMethod, _localTypes); //indexLocalVars = nextIndex; nextIndex = InitLocalsVars(defMethod.Body, _localTypes, nextIndex); //countLocalVars = nextIndex - indexLocalVars; maxTempIndex = nextIndex; nextTempIndex = nextIndex; nextTempLabel = 0xFFFE; InitLocalsRefs(defMethod, _localTypes); localTypes = _localTypes.ToArray(); if (defMethod.Body.HasExceptionHandlers) { // jvm resets the stackmap on each exception clause, to the state it // was on entry to the try block. this means any locals initialized // within a try block are 'lost' outside the try, and cause stackmap // conflicts. two examples: // // (1) filter condition code (in a filter clause) sets a local to the // exception object, then uses that local in a separate catch clause. // (2) returning a value from a 'try' can initialize a local with the // return value, but the 'return' instruction is outside the 'try'. // // to avoid stackmap conflicts, we have to initialize the variables // in advance. ideally, on entry to each 'try' block, and only for // those variables actually referenced in the block. but this causes // several complications, so instead we go for a simpler approach, // and always initialize all variables in advance. InitializeUnusedVariables(); } instCountAfterInitCode = code.Instructions.Count; }
public CilMethod EnterMethod(MethodReference fromMethod) { var myMethod = CilMethod.From(fromMethod); var declaringType = fromMethod.DeclaringType; int parameterIndex = (myMethod.HasThisArg ? 1 : 0); foreach (var p in myMethod.Parameters) { parameterIndex += p.Type.Category; } if (myMethod.IsConstructor || myMethod.IsStatic) { if (declaringType.HasGenericParameters) { parameterIndex += EnterGenericProvider(declaringType, null, parameterIndex); } } if (declaringType.IsGenericInstance) { var genericInstance = (GenericInstanceType)declaringType; EnterGenericInstance(genericInstance, genericInstance.ElementType); } if (fromMethod.HasGenericParameters) { EnterGenericProvider(fromMethod, null, parameterIndex); } if (fromMethod.IsGenericInstance) { var genericInstance = (GenericInstanceMethod)fromMethod; EnterGenericInstance(genericInstance, genericInstance.ElementMethod); } return(myMethod); }
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; } } } }
public static JavaClass MakeGenericClass(JavaClass fromClass, CilType fromType) { // if the generic class has static fields or a static initializer // then we need to move those into a separate class that can be // instantiated multiple times for multiple separate instances, // one for each concrete implementation of the generic type. int numGeneric = fromType.GenericParameters.Count; var dataClass = MoveStaticFields(fromClass, null); dataClass = MoveStaticInit(fromClass, dataClass); if (dataClass != null) { FixConstructorInData(dataClass, numGeneric); } // a generic class implements the IGenericObject interface, // and has a generic-type field for the concrete implementation // of the generic type and generic arguments CreateGenericTypeFields(fromClass, numGeneric); BuildGetTypeMethod(fromClass, fromType); return(dataClass); // // move any static fields from the generic class, // as instance fields in a new class // JavaClass MoveStaticFields(JavaClass fromClass, JavaClass dataClass) { var fields = fromClass.Fields; if (fields == null) { return(dataClass); } int n = fields.Count; for (int i = 0; i < n;) { var fld = fields[i]; if ((fld.Flags & JavaAccessFlags.ACC_STATIC) == 0) { i++; continue; } if (((CilType)fld.Type).IsLiteral) { i++; continue; } if (dataClass == null) { dataClass = CreateClass(fromClass); } if (fld.Constant != null) { throw CilMain.Where.Exception($"initializer in static field '{fld.Name}' in generic class"); } fields.RemoveAt(i); n--; fld.Flags &= ~JavaAccessFlags.ACC_STATIC; dataClass.Fields.Add(fld); } return(dataClass); } // // move the static constructor/initializer // from the generic class to the new data class // JavaClass MoveStaticInit(JavaClass fromClass, JavaClass dataClass) { var methods = fromClass.Methods; int n = methods.Count; for (int i = 0; i < n;) { var mth = methods[i]; if (mth.Name != "<clinit>") { i++; continue; } if (dataClass == null) { dataClass = CreateClass(fromClass); } methods.RemoveAt(i); n--; mth.Name = "<init>"; mth.Class = dataClass; mth.Flags = JavaAccessFlags.ACC_PUBLIC; dataClass.Methods.Add(mth); } return(dataClass); } // // create a constructor if there was no static initializer, // or inject a call to super class constructor // void FixConstructorInData(JavaClass dataClass, int numGeneric) { JavaCode code; bool insertReturn; if (dataClass.Methods.Count == 0) { code = CilMethod.CreateConstructor(dataClass, numGeneric, true); insertReturn = true; code.MaxStack = 1; code.MaxLocals = 1 + numGeneric; } else { code = dataClass.Methods[0].Code; if (code.MaxStack < 1) { code.MaxStack = 1; } // we are injecting a call to super constructor at the very top, // so local 0 should have the proper type, not uninitializedThis code.StackMap.SetLocalInAllFrames( 0, CilType.From(new JavaType(0, 0, dataClass.Name)), null); insertReturn = false; } code.Instructions.Insert(0, new Instruction( 0x19 /* aload */, null, (int)0, 0xFFFF)); code.Instructions.Insert(1, new Instruction( 0xB7 /* invokespecial */, JavaType.ObjectType, new JavaMethodRef("<init>", JavaType.VoidType), 0xFFFF)); // the static initializer can call static methods on its own type, // and those methods can invoke system.RuntimeType.GetType() to get // a reference to the generic type that is still being initialized. // and more importantly, a reference to the the static-generic data // object that is constructed by this method. to make the object // available to such access, we call system.RuntimeType.SetStatic(). // see also system.RuntimeType.MakeGenericType/MakeGenericType(). code.Instructions.Insert(2, new Instruction( 0x19 /* aload */, null, (int)0, 0xFFFF)); code.Instructions.Insert(3, new Instruction( 0xB8 /* invokestatic */, CilType.SystemRuntimeTypeType, new JavaMethodRef("SetStatic", JavaType.VoidType, JavaType.ObjectType), 0xFFFF)); if (insertReturn) { code.NewInstruction(JavaType.VoidType.ReturnOpcode, null, null, 0xFFFF); } } // // create the new data class // JavaClass CreateClass(JavaClass fromClass) => CilMain.CreateInnerClass(fromClass, fromClass.Name + "$$static", 0, markGenericEntity: true); // // create a private instance field to hold the runtime type // for a particular combination of generic type and arguments // void CreateGenericTypeFields(JavaClass fromClass, int numGeneric) { var fld = new JavaField(); fld.Name = ConcreteTypeField.Name; fld.Type = ConcreteTypeField.Type; fld.Class = fromClass; fld.Flags = JavaAccessFlags.ACC_PRIVATE; if (fromClass.Fields == null) { fromClass.Fields = new List <JavaField>(); } fromClass.Fields.Add(fld); } }
bool ConvertVirtualToStaticCall(CilType callClass, CilMethod callMethod) { if (callClass.ClassName == callClass.JavaName) { if (callClass.ClassName == "system.FunctionalInterfaceDelegate" && callMethod.Name == "AsInterface") { // a call to system.FunctionalInterfaceDelegate::AsInterface() // should be fixed to expect an 'object' return type, and then // cast that return type to the proper interface var tempMethod = new JavaMethodRef(callMethod.Name, JavaType.ObjectType); code.NewInstruction(0xB6 /* invokevirtual */, callClass, tempMethod); code.NewInstruction(0xC0 /* checkcast */, callMethod.ReturnType, null); stackMap.PopStack(CilMain.Where); stackMap.PushStack(callMethod.ReturnType); return(true); } if (callMethod.IsExternal && NativeMethodClasses.TryGetValue(callClass.ClassName, out var altClassName)) { // a call to an instance method System.NameSpace.Class::Method, // which is implemented only as a native method, is translated // into a static call to some other class where the method is // implemented. such implementing classes are listed in the // NativeMethodClasses dictionary the bottom of this file. var altMethodName = callMethod.Name; int altMethodNameIdx = altMethodName.IndexOf(CilMain.OPEN_PARENS); if (altMethodNameIdx != -1) { altMethodName = altMethodName.Substring(0, altMethodNameIdx); } var tempMethod = new JavaMethodRef(altMethodName, callMethod.ReturnType); var parameters = new List <JavaFieldRef>(callMethod.Parameters); parameters.Insert(0, new JavaFieldRef("this", callClass)); tempMethod.Parameters = parameters; code.NewInstruction(0xB8 /* invokestatic */, new JavaType(0, 0, altClassName), tempMethod); ClearMethodArguments(callMethod, false); PushMethodReturnType(callMethod); return(true); } // don't bother adjusting the call if the program explicitly // refers to the java class rather than the simulated wrapper, // e.g. to java.lang.Throwable rather than System.Exception return(false); } if (callClass.Equals(JavaType.StringType) || callClass.Equals(JavaType.ThrowableType) || (callClass.Equals(JavaType.ObjectType) && callMethod.Name == "GetType" && callMethod.ToDescriptor() == "()Lsystem/Type;")) { // we map some basic .Net types their java counterparts, so we // can't invoke .Net instance methods on them directly. instead, // we invoke a static method on our helper class. for example: // ((System.String)x).CompareTo(y) -> system.String.CompareTo(x,y) var tempMethod = new JavaMethodRef(callMethod.Name, callMethod.ReturnType); var parameters = new List <JavaFieldRef>(callMethod.Parameters); parameters.Insert(0, new JavaFieldRef("this", callClass)); tempMethod.Parameters = parameters; if (callClass.Equals(JavaType.ThrowableType) && callMethod.Name == "system-Exception-GetType") { // undo the effect of CilMethod::MethodIsShadowing upon calling // the virtual/overriding GetType() from System.Exception, and // instead call the static system.Object.GetType() tempMethod.Name = "GetType"; callClass = CilType.From(new JavaType(0, 0, "system.Object")); parameters[0].Type = JavaType.ObjectType; } else { CilMethod.FixNameForVirtualToStaticCall(tempMethod, callClass); } code.NewInstruction(0xB8 /* invokestatic */, new JavaType(0, 0, callClass.JavaName), tempMethod); ClearMethodArguments(callMethod, false); PushMethodReturnType(callMethod); return(true); } if (callClass.JavaName == null && callClass.Equals(JavaType.ClassType) && callMethod.Name == "GetRuntimeType") { // convert virtual call to RuntimeTypeHandle.GetRuntimeType // to a static call to system.RuntimeType code.NewInstruction(0xB8 /* invokestatic */, CilType.SystemRuntimeTypeType, new JavaMethodRef( callMethod.Name, callMethod.ReturnType, JavaType.ObjectType)); ClearMethodArguments(callMethod, false); PushMethodReturnType(callMethod); return(true); } return(false); }
int InitLocalsArgs(CilMethod myMethod, List <CilType> localTypes) { var parameters = myMethod.WithGenericParameters.Parameters; int numArgs = parameters.Count; CilType thisType; if (myMethod.HasThisArg) { if (myMethod.IsConstructor) { thisType = CilType.From(JavaStackMap.UninitializedThis); } else { thisType = myMethod.DeclType; if (thisType.IsValueClass) { thisType = thisType.MakeByRef(); } } stackMap.SetLocal(0, thisType); numArgs++; } else { thisType = null; } argToLocalMap = new int[numArgs]; int nextArg = 0; int nextIndex = 0; if (thisType != null) { argToLocalMap[nextArg++] = nextIndex++; localTypes.Add(thisType); numArgs--; } for (int i = 0; i < numArgs; i++) { var argType = (CilType)parameters[i].Type; stackMap.SetLocal(nextIndex, argType); var genericType = argType.GetMethodGenericParameter(); if (genericType != null) { if (genericType.IsArray && genericType.IsGenericParameter) { // note that GenericArrayType is compared by reference // in CodeArrays, to detect an array of a generic type T[] argType = CodeArrays.GenericArrayType; } else { argType = genericType; } } argToLocalMap[nextArg++] = nextIndex; nextIndex += argType.Category; localTypes.Add(argType); while (nextIndex > localTypes.Count) { localTypes.Add(null); } } return(nextIndex); }
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()); } }
void Store(CilType elemType, Mono.Cecil.Cil.Instruction inst) { stackMap.PopStack(CilMain.Where); // value stackMap.PopStack(CilMain.Where); // index var arrayType = stackMap.PopStack(CilMain.Where) as CilType; // array var arrayElemType = arrayType.AdjustRank(-arrayType.ArrayRank); if (elemType == null) { elemType = arrayElemType; } /*Console.WriteLine("(STORE) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType)) { // stelem.any T into generic array T[] code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, StoreArrayMethod); } else if (arrayElemType.IsValueClass && elemType.IsValueClass) { // storing a value type into an array of value types. // we use ValueType.ValueCopy to write over the element. int localIndex = locals.GetTempIndex(elemType); code.NewInstruction(elemType.StoreOpcode, null, localIndex); code.NewInstruction(arrayType.LoadArrayOpcode, null, null); code.NewInstruction(elemType.LoadOpcode, null, localIndex); locals.FreeTempIndex(localIndex); // we can pass any type that is not a generic parameter GenericUtil.ValueCopy(CilType.SystemTypeType, code, true); } else if (arrayType.ArrayRank > 1) { // always 'aastore' if multidimensional array code.NewInstruction(arrayType.StoreArrayOpcode, null, null); } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // stelem.i2 with a char[] array, should be 'castore' not 'sastore' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } else { // Android AOT crashes the compilation if an immediate value // is stored into a byte or short array, and the value does // not fit within the range -128..127 or -32768..32767. // simply checing if the previous instruction loaded the // constant is not enough, because due to method inlining // by the Android ART JIT, the immediate value might actually // originate in a calling method. // so we always force the value into range using i2b/i2s. // see also: CodeNumber::ConvertToInteger if (arrayType.PrimitiveType == TypeCode.Boolean || arrayType.PrimitiveType == TypeCode.SByte || arrayType.PrimitiveType == TypeCode.Byte) { code.NewInstruction(0x91 /* i2b */, null, null); } else if (arrayType.PrimitiveType == TypeCode.Int16) { code.NewInstruction(0x93 /* i2s */, null, null); } } if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); } code.NewInstruction(elemType.StoreArrayOpcode, null, null); } }
public CilInterfaceMethod(CilMethod fromMethod) { var name = fromMethod.Name; var idx = name.IndexOf(CilMain.OPEN_PARENS); if (idx != -1) { name = name.Substring(0, idx); } idx = name.IndexOf("--"); if (idx != -1) { name = name.Substring(0, idx); } idx = name.LastIndexOf('-'); if (idx != -1) { name = name.Substring(idx + 1); } ResolvedGenericTypes = ""; var returnType = (CilType)fromMethod.ReturnType; if (returnType.IsGenericParameter) { var genericMark = CilMain.GenericStack.Mark(); var(type, index) = CilMain.GenericStack.Resolve(returnType.JavaName); ResolvedGenericTypes = $"{type},{index};"; if (index == 0) { returnType = type; } CilMain.GenericStack.Release(genericMark); } int n = fromMethod.Parameters.Count; var parameters = new List <CilType>(n); for (int i = 0; i < n; i++) { var parameter = (CilType)fromMethod.Parameters[i].Type; if (parameter.IsGenericParameter) { parameter = parameter.GenericParameters[0]; var genericMark = CilMain.GenericStack.Mark(); var(type, index) = CilMain.GenericStack.Resolve(parameter.JavaName); ResolvedGenericTypes += $"{type},{index};"; if (index == 0) { if (parameter.ArrayRank != 0) { type = type.AdjustRank(parameter.ArrayRank); } parameter = type; } CilMain.GenericStack.Release(genericMark); } parameters.Add(parameter); } Method = fromMethod; SimpleName = name; Parameters = parameters; ReturnType = returnType; }
public static List <JavaClass> BuildProxyMethods(List <CilInterface> 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 == "<init>") { 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 <JavaClass> 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 <JavaClass>(); 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("<init>", 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("<init>", 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 <JavaField>(1); } parentClass.Fields.Add(fld); } }
public static void BuildGenericProxy(CilInterfaceMethod ifcMethod, /*string methodSuffix,*/ CilType intoType, List <CilInterfaceMethod> 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 a second method matches, and the set of generic types // in its signature exactly matches the interface method we // are looking for, then prefer this method. when a class // implements same-name methods from multiple interfaces, // this is needed to pick the right method. // see also ResolvedGenericTypes in CilInterfaceMethod. else if (clsMethod.ResolvedGenericTypes.Length != 0 && clsMethod.ResolvedGenericTypes == ifcMethod.ResolvedGenericTypes) { 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); }
bool Translate_Newobj(CilMethod callMethod) { if (callMethod.IsStatic) { return(false); } var newClass = callMethod.DeclType; if (callMethod.IsArrayMethod) { arrays.New(newClass, callMethod.Parameters.Count); return(true); } if (newClass.IsValueClass) { if (!callMethod.IsValueInit) { return(false); } } else { if (!callMethod.IsConstructor) { return(false); } if (newClass.Equals(JavaType.StringType) && code.Method.Class.Name != newClass.JavaName) { // redirect the System.String constructor to system.String.New var newMethod = new JavaMethodRef("New", newClass); newMethod.Parameters = callMethod.Parameters; code.NewInstruction(0xB8 /* invokestatic */, new JavaType(0, 0, newClass.JavaName), newMethod); int n = callMethod.Parameters.Count; while (n-- > 0) { stackMap.PopStack(CilMain.Where); } stackMap.PushStack(newClass); return(true); } if (newClass.Equals(JavaType.ThrowableType)) { // generally we translate System.Exception to java.lang.Throwable, // but not when allocating a new object newClass = CilType.From(new JavaType(0, 0, newClass.JavaName)); } } // // save any parameters pushed for the call to the constructor // int localIndex = SaveMethodArguments(callMethod); code.NewInstruction(0xBB /* new */, newClass.AsWritableClass, null); stackMap.PushStack(newClass); code.NewInstruction(0x59 /* dup */, null, null); stackMap.PushStack(newClass); if (newClass.IsValueClass) { // new value type object. first call the default constructor, // to allocate the object. then call the constructor provided // in the Newobj instruction, to initialize the new object var defaultConstructor = new CilMethod(newClass); PushGenericArguments(defaultConstructor); code.NewInstruction(0xB7 /* invokespecial */, newClass, defaultConstructor.WithGenericParameters); // the defaul constructor accepted generic parameters, if any, // and they should be discarded now int numGeneric = defaultConstructor.WithGenericParameters.Parameters.Count; while (numGeneric-- > 0) { stackMap.PopStack(CilMain.Where); } // if Newobj specifies a non-default constructor, then call it int numNonGeneric = callMethod.Parameters.Count; if (numNonGeneric != 0) { code.NewInstruction(0x59 /* dup */, null, null); LoadMethodArguments(callMethod, localIndex); code.NewInstruction(0xB6 /* invokevirtual */, newClass, callMethod); while (numNonGeneric-- > 0) { stackMap.PopStack(CilMain.Where); } } stackMap.PopStack(CilMain.Where); } else { // // new reference type object // LoadMethodArguments(callMethod, localIndex); PushGenericArguments(callMethod); code.NewInstruction(0xB7 /* invokespecial */, newClass.AsWritableClass, callMethod.WithGenericParameters); ClearMethodArguments(callMethod, false); } return(true); }
public static JavaMethod BuildPlainProxy(CilInterfaceMethod ifcMethod, CilType intoType, List <CilInterfaceMethod> 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 <JavaCode.Instruction>(); // // 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); }
void Load(CilType arrayType, CilType elemType, Mono.Cecil.Cil.Instruction inst) { if (arrayType == null) { arrayType = elemType.AdjustRank(1); } /*Console.WriteLine("(LOAD) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType) || elemType.IsGenericParameter || arrayType.IsGenericParameter) { code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, LoadArrayMethod); if (elemType.ArrayRank != 0) { elemType = GenericArrayType; } } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // ldelem.i2 with a char[] array, should be 'caload' not 'saload' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } code.NewInstruction(elemType.LoadArrayOpcode, null, null); if (elemType.PrimitiveType == TypeCode.Byte) { // unsigned byte result should be truncated to 8-bits // (unless already followed by "ldc.i4 255 ; and") bool followedByAndWith255 = CodeBuilder.IsLoadConstant(inst.Next) == 0xFF && inst.Next.Next?.OpCode.Code == Code.And; if (!followedByAndWith255) { stackMap.PushStack(JavaType.IntegerType); code.NewInstruction(0x12 /* ldc */, null, (int)0xFF); code.NewInstruction(0x7E /* iand */, null, null); stackMap.PopStack(CilMain.Where); } } if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); if (elemType.IsValueClass) { code.NewInstruction(0xC0 /* checkcast */, elemType, null); } } } stackMap.PushStack(elemType); }
void Store(CilType elemType) { stackMap.PopStack(CilMain.Where); // value stackMap.PopStack(CilMain.Where); // index var arrayType = stackMap.PopStack(CilMain.Where) as CilType; // array var arrayElemType = arrayType.AdjustRank(-arrayType.ArrayRank); if (elemType == null) { elemType = arrayElemType; } /*Console.WriteLine("(STORE) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType)) { // stelem.any T into generic array T[] code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, StoreArrayMethod); } else if (arrayElemType.IsValueClass && elemType.IsValueClass) { // storing a value type into an array of value types. // we use ValueType.ValueCopy to write over the element. int localIndex = locals.GetTempIndex(elemType); code.NewInstruction(elemType.StoreOpcode, null, localIndex); code.NewInstruction(arrayType.LoadArrayOpcode, null, null); code.NewInstruction(elemType.LoadOpcode, null, localIndex); locals.FreeTempIndex(localIndex); // we can pass any type that is not a generic parameter GenericUtil.ValueCopy(CilType.SystemTypeType, code, true); } else if (arrayType.ArrayRank > 1) { // always 'aastore' if multidimensional array code.NewInstruction(arrayType.StoreArrayOpcode, null, null); } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // stelem.i2 with a char[] array, should be 'castore' not 'sastore' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); } code.NewInstruction(elemType.StoreArrayOpcode, null, null); } }
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 <JavaCode.Instruction>(); // // 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); }
void LoadObject(Code cilOp, object data) { if (data is TypeReference typeRef) { var dataType = CilType.From(typeRef); var fromType = (CilType)code.StackMap.PopStack(CilMain.Where); if (CodeSpan.LoadStore(true, fromType, null, dataType, code)) { return; } if ((!dataType.IsReference) && cilOp == Code.Ldobj && fromType is BoxedType fromBoxedType && dataType.PrimitiveType == fromBoxedType.UnboxedType.PrimitiveType) { // 'ldobj primitive' with a corresponding boxed type on the stack. // we implement by unboxing the boxed type into a primitive value. fromBoxedType.GetValue(code); stackMap.PushStack(fromBoxedType.UnboxedType); return; } if (dataType.IsGenericParameter || (dataType.IsValueClass && dataType.Equals(fromType))) { code.StackMap.PushStack(dataType); if (SkipClone(cilInst.Next, fromType)) { // see below for the several cases where we determine // that we can safely avoid making a clone of the value code.NewInstruction(0x00 /* nop */, null, null); } else if (dataType.IsGenericParameter) { GenericUtil.ValueClone(code); } else if (cilOp == Code.Ldobj) { code.NewInstruction(0x00 /* nop */, null, null); } else { CilMethod.ValueMethod(CilMethod.ValueClone, code); } return; } if (dataType.IsReference && cilOp == Code.Box) { // 'box' is permitted on reference types, we treat it as a cast code.StackMap.PushStack(fromType); CastToClass(data); return; } if (!dataType.IsReference) { var boxedType = new BoxedType(dataType, false); code.StackMap.PushStack(boxedType); boxedType.BoxValue(code); return; } } throw new InvalidProgramException(); bool SkipClone(Mono.Cecil.Cil.Instruction next, CilType checkType) { if (checkType.IsByReference) { return(true); } if (next == null) { return(false); } var op = next.OpCode.Code; if (IsBrTrueBrFalseIsInst(op)) { // if 'ldobj' or 'box' is followed by a check for null, // we don't actually need to clone just for the test return(true); } if (op == Code.Box) { if (next.Operand is TypeReference nextTypeRef) { // 'ldobj' may be followed by 'box', to load the value // of a byref value type, and then box it into an object. // effectively we only need to clone once, in such a case. return(CilType.From(nextTypeRef).Equals(checkType)); } } var(storeType, _) = locals.GetLocalFromStoreInst(op, next.Operand); if (storeType != null) { // 'ldobj' or 'box' may be followed by a store instruction. // if storing into a variable of the same value type, the // next instruction will copy the value, so skip clone. return(storeType.Equals(checkType)); } if (op == Code.Ret && checkType.IsClonedAtTop) { // if the value on the stack was cloned/boxed at the top of // the method, then we can avoid clone and return it directly. // see also: CilType.MakeClonedAtTop and its callers. return(true); } if (op == Code.Unbox_Any) { if (next.Operand is TypeReference nextTypeRef) { // 'ldobj' or 'box' may be followed by 'unbox' and // then one of the instructions above, e.g. 'brtrue' // or 'stloc'. we still want to detect such a case // and prevent a needless clone. return(SkipClone(next.Next, CilType.From(nextTypeRef))); } } return(false); } }
bool Translate_Call(CilMethod callMethod) { var currentClass = method.DeclType; var callClass = callMethod.DeclType; byte op; if (callMethod.IsStatic) { if (ConvertCallToNop()) { return(true); } if (callClass.Equals(JavaType.ObjectType) || callClass.Equals(JavaType.StringType)) { // generally we translate System.Object to java.lang.Object, // and System.String to java.lang.String, // but not in the case of a static method call callClass = CilType.From(new JavaType(0, 0, callClass.JavaName)); } else if (IsSystemTypeOperatorOrIsCallToIsInterface(callClass, callMethod)) { // rename System.Type operators == and != to system.RuntimeType, and // rename call to RuntimeTypeHandle.IsInterface to system.RuntimeType callClass = CilType.SystemRuntimeTypeType; } else if (callMethod.IsExternal && NativeMethodClasses.TryGetValue(callClass.ClassName, out var altClassName)) { // when the call target is a native methods, redirect it callClass = CilType.From(new JavaType(0, 0, altClassName)); } op = 0xB8; // invokestatic } else if (callMethod.IsConstructor || currentClass.IsDerivedFrom(callClass)) { if (callClass.Equals(JavaType.ThrowableType)) { // generally we translate System.Exception to java.lang.Throwable, // but not in the case of a super constructor call callClass = CilType.From(new JavaType(0, 0, callClass.JavaName)); } if (ConvertVirtualToStaticCall(callClass, callMethod)) { return(true); } if (callMethod.IsVirtual && currentClass.Equals(callClass)) { // Android 'D8' does not support 'invokespecial' on a method // on the same class, unless the method is marked final. // if we know the target method is a virtual method, then // it surely is not marked final, so use 'invokevirtual'. op = 0xB6; // invokevirtual } else { op = 0xB7; // invokespecial } if (callMethod.Name == "clone" && callMethod.Parameters.Count == 0) { // if calling clone on the super object, implement Cloneable code.Method.Class.AddInterface("java.lang.Cloneable"); } } else { if (ConvertVirtualToStaticCall(callClass, callMethod)) { return(true); } op = 0xB6; // invokevirtual } if (callMethod.IsArrayMethod) { arrays.Call(callMethod, cilInst); } else { CheckAndBoxArguments(callMethod, (op == 0xB6)); PushGenericArguments(callMethod); code.NewInstruction(op, callClass.AsWritableClass, callMethod.WithGenericParameters); ClearMethodArguments(callMethod, (op == 0xB7)); PushMethodReturnType(callMethod); } return(true); bool IsSystemTypeOperatorOrIsCallToIsInterface(CilType callClass, CilMethod callMethod) { if (callClass.Equals(CilType.SystemTypeType) && ( callMethod.Name == "op_Equality" || callMethod.Name == "op_Inequality")) { return(true); } if (callClass.Equals(JavaType.ClassType) && callMethod.Name == "IsInterface") { return(true); } return(false); } }