IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
LambdaMetafactory.cs
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 Jeroen Frijters
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
11
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
19
20 Jeroen Frijters
21 jeroen@frijters.net
22
23*/
24using System;
25using System.Collections.Generic;
26using System.Diagnostics;
27
28using IKVM.ByteCode;
29
30#if IMPORTER
31using IKVM.Reflection;
34
35using Type = IKVM.Reflection.Type;
36#else
37using System.Reflection;
38using System.Reflection.Emit;
39
40#endif
41
42namespace IKVM.Runtime
43{
44
48 sealed class LambdaMetafactory
49 {
50
51 MethodBuilder getInstance;
52
62 internal static bool Emit(RuntimeByteCodeJavaType.FinishContext context, ClassFile classFile, int constantPoolIndex, ClassFile.ConstantPoolItemInvokeDynamic cpi, CodeEmitter ilgen)
63 {
64 var bsm = classFile.GetBootstrapMethod(cpi.BootstrapMethod);
65
66 // only support emitting IL for LambdaMetafactory bootstrap methods
67 if (!IsLambdaMetafactory(classFile, bsm) && !IsLambdaAltMetafactory(classFile, bsm))
68 return false;
69
70 // we associate a single LambdaMetafactory instance for each dynamic invoke constant
71 var lmf = context.GetValue<LambdaMetafactory>(constantPoolIndex);
72 if (lmf.getInstance == null && !lmf.EmitImpl(context, classFile, cpi, bsm, ilgen))
73 {
74#if IMPORTER
75 if (context.TypeWrapper.ClassLoader.DisableDynamicBinding)
76 context.TypeWrapper.ClassLoader.Diagnostics.UnableToCreateLambdaFactory();
77#endif
78 return false;
79 }
80
81 // call getInstance at the call site
82 ilgen.Emit(OpCodes.Call, lmf.getInstance);
83 return true;
84 }
85
96 {
97 if (HasUnloadable(cpi))
98 {
99 Fail("cpi has unloadable");
100 return false;
101 }
102
103 var serializable = false;
104 var markers = Array.Empty<RuntimeJavaType>();
105 ClassFile.ConstantPoolItemMethodType[] bridges = null;
106
107 // argument count >3 indicates altMetafactory call
108 // scan for flags, markers and bridges
109 if (bsm.ArgumentCount > 3)
110 {
111 var flags = (AltFlags)classFile.GetConstantPoolConstantInteger((IntegerConstantHandle)bsm.GetArgument(3));
112 serializable = (flags & AltFlags.Serializable) != 0;
113 int argpos = 4;
114
115 if ((flags & AltFlags.Markers) != 0)
116 {
117 markers = new RuntimeJavaType[classFile.GetConstantPoolConstantInteger((IntegerConstantHandle)bsm.GetArgument(argpos++))];
118 for (int i = 0; i < markers.Length; i++)
119 {
120 if ((markers[i] = classFile.GetConstantPoolClassType((ClassConstantHandle)bsm.GetArgument(argpos++))).IsUnloadable)
121 {
122 Fail("unloadable marker");
123 return false;
124 }
125 }
126 }
127
128 if ((flags & AltFlags.Bridges) != 0)
129 {
130 bridges = new ClassFile.ConstantPoolItemMethodType[classFile.GetConstantPoolConstantInteger((IntegerConstantHandle)bsm.GetArgument(argpos++))];
131 for (int i = 0; i < bridges.Length; i++)
132 {
133 bridges[i] = classFile.GetConstantPoolConstantMethodType((MethodTypeConstantHandle)bsm.GetArgument(argpos++));
134 if (HasUnloadable(bridges[i]))
135 {
136 Fail("unloadable bridge");
137 return false;
138 }
139 }
140 }
141 }
142
143 var samMethodType = classFile.GetConstantPoolConstantMethodType((MethodTypeConstantHandle)bsm.GetArgument(0));
144 var implMethod = classFile.GetConstantPoolConstantMethodHandle((MethodHandleConstantHandle)bsm.GetArgument(1));
145 var instantiatedMethodType = classFile.GetConstantPoolConstantMethodType((MethodTypeConstantHandle)bsm.GetArgument(2));
146 if (HasUnloadable(samMethodType) || HasUnloadable((ClassFile.ConstantPoolItemMI)implMethod.MemberConstantPoolItem) || HasUnloadable(instantiatedMethodType))
147 {
148 Fail("bsm args has unloadable");
149 return false;
150 }
151
152 var interfaceType = cpi.GetRetType();
153 if (!CheckSupportedInterfaces(context.TypeWrapper, interfaceType, markers, bridges, out var methodList))
154 {
155 Fail("unsupported interface");
156 return false;
157 }
158
159 if (serializable && Array.Exists(methodList, mw => mw.Name == "writeReplace" && mw.Signature == "()Ljava.lang.Object;"))
160 {
161 Fail("writeReplace");
162 return false;
163 }
164
165 if (!IsSupportedImplMethod(implMethod, context.TypeWrapper, cpi.GetArgTypes(), instantiatedMethodType))
166 {
167 Fail("implMethod " + implMethod.MemberConstantPoolItem.Class + "::" + implMethod.MemberConstantPoolItem.Name + implMethod.MemberConstantPoolItem.Signature);
168 return false;
169 }
170
171 // get implementation parameters
172 var implParameters = GetImplParameters(implMethod);
173 CheckConstraints(instantiatedMethodType, samMethodType, cpi.GetArgTypes(), implParameters);
174
175 // check bridges
176 if (bridges != null)
177 {
178 foreach (var bridge in bridges)
179 {
180 if (bridge.Signature == samMethodType.Signature)
181 {
182 Fail("bridge signature matches sam");
183 return false;
184 }
185
186 if (!CheckConstraints(instantiatedMethodType, bridge, cpi.GetArgTypes(), implParameters))
187 {
188 Fail("bridge constraints");
189 return false;
190 }
191 }
192 }
193
194 // check that we can adapt instantiated return type to interface return type
195 if (instantiatedMethodType.GetRetType() != context.Context.PrimitiveJavaTypeFactory.VOID)
196 {
197 var Rt = instantiatedMethodType.GetRetType();
198 var Ra = GetImplReturnType(implMethod);
199 if (Ra == context.Context.PrimitiveJavaTypeFactory.VOID || !IsAdaptable(Ra, Rt, true))
200 {
201 Fail("The return type Rt is void, or the return type Ra is not void and is adaptable to Rt");
202 return false;
203 }
204 }
205
206 // find desired functional interface method
207 RuntimeJavaMethod interfaceMethod = null;
208 var methods = new List<RuntimeJavaMethod>();
209 foreach (var mw in methodList)
210 {
211 // method exactly matches interface method signature
212 if (mw.Name == cpi.Name && mw.Signature == samMethodType.Signature)
213 {
214 interfaceMethod = mw;
215 methods.Add(mw);
216 continue;
217 }
218
219 if (mw.IsAbstract && !IsObjectMethod(mw))
220 {
221 methods.Add(mw);
222 continue;
223 }
224 }
225
226 // check interface method
227 if (interfaceMethod == null || !interfaceMethod.IsAbstract || IsObjectMethod(interfaceMethod) || !MatchSignatures(interfaceMethod, samMethodType))
228 {
229 Fail("interfaceMethod");
230 return false;
231 }
232
233 // define a new anonymous class to implement the functional interface
234 var tb = context.DefineAnonymousClass();
235
236 // we're not implementing the interfaces recursively (because we don't care about .NET Compact anymore),
237 // but should we decide to do that, we'd need to somehow communicate to AnonymousTypeWrapper what the 'real' interface is
238 tb.AddInterfaceImplementation(interfaceType.TypeAsBaseType);
239 if (serializable && Array.IndexOf(markers, context.Context.JavaBase.TypeOfJavaIoSerializable) == -1)
240 tb.AddInterfaceImplementation(context.Context.JavaBase.TypeOfJavaIoSerializable.TypeAsBaseType);
241
242 // implement marker interfaces
243 foreach (var marker in markers)
244 tb.AddInterfaceImplementation(marker.TypeAsBaseType);
245
246 getInstance = CreateConstructorAndDispatch(context, cpi, tb, methods, implParameters, samMethodType, implMethod, instantiatedMethodType, serializable);
247 AddDefaultInterfaceMethods(context, methodList, tb);
248 return true;
249 }
250
251 [Conditional("TRACE_LAMBDA_METAFACTORY")]
252 static void Fail(string msg)
253 {
254 Console.WriteLine("Fail: " + msg);
255 }
256
265 static bool CheckConstraints(ClassFile.ConstantPoolItemMethodType instantiatedMethodType, ClassFile.ConstantPoolItemMethodType methodType, RuntimeJavaType[] args, RuntimeJavaType[] implParameters)
266 {
267 if (!IsSubTypeOf(instantiatedMethodType, methodType))
268 {
269 Fail("instantiatedMethodType <= methodType");
270 return false;
271 }
272
273 if (args.Length + methodType.GetArgTypes().Length != implParameters.Length)
274 {
275 Fail("K + N = M");
276 return false;
277 }
278
279 for (int i = 0, K = args.Length; i < K; i++)
280 {
281 if (args[i] == implParameters[i])
282 {
283 // ok
284 }
285 else if (args[i].IsPrimitive || implParameters[i].IsPrimitive || !args[i].IsSubTypeOf(implParameters[i]))
286 {
287 Fail("For i=1..K, Di = Ai");
288 return false;
289 }
290 }
291
292 for (int i = 0, N = methodType.GetArgTypes().Length, k = args.Length; i < N; i++)
293 {
294 if (!IsAdaptable(instantiatedMethodType.GetArgTypes()[i], implParameters[i + k], false))
295 {
296 Fail("For i=1..N, Ti is adaptable to Aj, where j=i+k");
297 return false;
298 }
299 }
300
301 return true;
302 }
303
304 static RuntimeJavaType[] GetImplParameters(ClassFile.ConstantPoolItemMethodHandle implMethod)
305 {
306 var mw = (RuntimeJavaMethod)implMethod.Member;
307 var parameters = mw.GetParameters();
308 if (mw.IsStatic || mw.IsConstructor)
309 return parameters;
310
311 return ArrayUtil.Concat(mw.DeclaringType, parameters);
312 }
313
314 static RuntimeJavaType GetImplReturnType(ClassFile.ConstantPoolItemMethodHandle implMethod)
315 {
316 return implMethod.Kind == MethodHandleKind.NewInvokeSpecial ? implMethod.Member.DeclaringType : ((RuntimeJavaMethod)implMethod.Member).ReturnType;
317 }
318
319 static bool IsAdaptable(RuntimeJavaType Q, RuntimeJavaType S, bool isReturn)
320 {
321 if (Q == S)
322 return true;
323
324 if (Q.IsPrimitive)
325 {
326 if (S.IsPrimitive)
327 {
328 // Q can be converted to S via a primitive widening conversion
329 switch (Q.SigName[0] | S.SigName[0] << 8)
330 {
331 case 'B' | 'S' << 8:
332 case 'B' | 'I' << 8:
333 case 'B' | 'J' << 8:
334 case 'B' | 'F' << 8:
335 case 'B' | 'D' << 8:
336 case 'S' | 'I' << 8:
337 case 'S' | 'J' << 8:
338 case 'S' | 'F' << 8:
339 case 'S' | 'D' << 8:
340 case 'C' | 'I' << 8:
341 case 'C' | 'J' << 8:
342 case 'C' | 'F' << 8:
343 case 'C' | 'D' << 8:
344 case 'I' | 'J' << 8:
345 case 'I' | 'F' << 8:
346 case 'I' | 'D' << 8:
347 case 'J' | 'F' << 8:
348 case 'J' | 'D' << 8:
349 case 'F' | 'D' << 8:
350 return true;
351 default:
352 return false;
353 }
354 }
355 else
356 {
357 // S is a supertype of the Wrapper(Q)
358 return GetBoxedPrimitiveType(Q).IsAssignableTo(S);
359 }
360 }
361 else if (isReturn)
362 {
363 return true;
364 }
365 else
366 {
367 if (S.IsPrimitive)
368 {
369 // If Q is a primitive wrapper, check that Primitive(Q) can be widened to S
370 var primitive = GetUnboxedPrimitiveType(Q);
371 return primitive != null && IsAdaptable(primitive, S, isReturn);
372 }
373 else
374 {
375 // for parameter types: S is a supertype of Q
376 return Q.IsAssignableTo(S);
377 }
378 }
379 }
380
387 static RuntimeJavaType GetBoxedPrimitiveType(RuntimeJavaType primitive)
388 {
389 Debug.Assert(primitive.IsPrimitive);
390 switch (primitive.SigName[0])
391 {
392 case 'Z':
393 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Boolean");
394 case 'B':
395 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Byte");
396 case 'S':
397 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Short");
398 case 'C':
399 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Character");
400 case 'I':
401 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Integer");
402 case 'J':
403 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Long");
404 case 'F':
405 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Float");
406 case 'D':
407 return primitive.Context.ClassLoaderFactory.LoadClassCritical("java.lang.Double");
408 default:
409 throw new InvalidOperationException();
410 }
411 }
412
418 static RuntimeJavaType GetUnboxedPrimitiveType(RuntimeJavaType wrapper)
419 {
420 switch (wrapper.Name)
421 {
422 case "java.lang.Boolean":
423 return wrapper.Context.PrimitiveJavaTypeFactory.BOOLEAN;
424 case "java.lang.Byte":
425 return wrapper.Context.PrimitiveJavaTypeFactory.BYTE;
426 case "java.lang.Short":
427 return wrapper.Context.PrimitiveJavaTypeFactory.SHORT;
428 case "java.lang.Character":
429 return wrapper.Context.PrimitiveJavaTypeFactory.CHAR;
430 case "java.lang.Integer":
431 return wrapper.Context.PrimitiveJavaTypeFactory.INT;
432 case "java.lang.Long":
433 return wrapper.Context.PrimitiveJavaTypeFactory.LONG;
434 case "java.lang.Float":
435 return wrapper.Context.PrimitiveJavaTypeFactory.FLOAT;
436 case "java.lang.Double":
437 return wrapper.Context.PrimitiveJavaTypeFactory.DOUBLE;
438 default:
439 return null;
440 }
441 }
442
443 static bool IsSubTypeOf(ClassFile.ConstantPoolItemMethodType instantiatedMethodType, ClassFile.ConstantPoolItemMethodType samMethodType)
444 {
445 var T = instantiatedMethodType.GetArgTypes();
446 var U = samMethodType.GetArgTypes();
447 if (T.Length != U.Length)
448 return false;
449
450 for (int i = 0; i < T.Length; i++)
451 if (!T[i].IsAssignableTo(U[i]))
452 return false;
453
454 var Rt = instantiatedMethodType.GetRetType();
455 var Ru = samMethodType.GetRetType();
456 return Rt.IsAssignableTo(Ru);
457 }
458
472 static MethodBuilder CreateConstructorAndDispatch(RuntimeByteCodeJavaType.FinishContext context, ClassFile.ConstantPoolItemInvokeDynamic cpi, TypeBuilder tb, List<RuntimeJavaMethod> methods, RuntimeJavaType[] implParameters, ClassFile.ConstantPoolItemMethodType samMethodType, ClassFile.ConstantPoolItemMethodHandle implMethod, ClassFile.ConstantPoolItemMethodType instantiatedMethodType, bool serializable)
473 {
474 var args = cpi.GetArgTypes();
475
476 // captured values
477 var capturedTypes = new Type[args.Length];
478 var capturedFields = new FieldBuilder[capturedTypes.Length];
479 for (int i = 0; i < capturedTypes.Length; i++)
480 {
481 capturedTypes[i] = args[i].TypeAsSignatureType;
482 var attr = FieldAttributes.Private;
483 if (i > 0 || !args[0].IsGhost)
484 attr |= FieldAttributes.InitOnly;
485
486 capturedFields[i] = tb.DefineField("arg$" + (i + 1), capturedTypes[i], attr);
487 }
488
489 // constructor
490 var ctor = ReflectUtil.DefineConstructor(tb, MethodAttributes.Assembly, capturedTypes);
491 var ilgen = context.Context.CodeEmitterFactory.Create(ctor);
492 ilgen.Emit(OpCodes.Ldarg_0);
493 ilgen.Emit(OpCodes.Call, context.Context.Types.Object.GetConstructor(Type.EmptyTypes));
494 for (int i = 0; i < capturedTypes.Length; i++)
495 {
496 ilgen.EmitLdarg(0);
497 ilgen.EmitLdarg(i + 1);
498 ilgen.Emit(OpCodes.Stfld, capturedFields[i]);
499 }
500 ilgen.Emit(OpCodes.Ret);
501 ilgen.DoEmit();
502
503 // instance getter
504 var getInstance = tb.DefineMethod("__<GetInstance>", MethodAttributes.Assembly | MethodAttributes.Static, cpi.GetRetType().TypeAsBaseType, capturedTypes);
505 var ilgenGet = context.Context.CodeEmitterFactory.Create(getInstance);
506
507 if (capturedTypes.Length == 0)
508 {
509 // use singleton for lambdas with no captures
510 var instField = tb.DefineField("inst", tb, FieldAttributes.Private | FieldAttributes.Static);
511
512 // static constructor
513 var cctor = ReflectUtil.DefineTypeInitializer(tb, context.TypeWrapper.ClassLoader);
514 var ilgenCCtor = context.Context.CodeEmitterFactory.Create(cctor);
515 ilgenCCtor.Emit(OpCodes.Newobj, ctor);
516 ilgenCCtor.Emit(OpCodes.Stsfld, instField);
517 ilgenCCtor.Emit(OpCodes.Ret);
518 ilgenCCtor.DoEmit();
519
520 // singleton instance
521 ilgenGet.Emit(OpCodes.Ldsfld, instField);
522 }
523 else
524 {
525 // new instance
526 for (int i = 0; i < capturedTypes.Length; i++)
527 ilgenGet.EmitLdarg(i);
528
529 ilgenGet.Emit(OpCodes.Newobj, ctor);
530 }
531
532 // the CLR verification rules about type merging mean we have to explicitly cast to the interface type here
533 ilgenGet.Emit(OpCodes.Castclass, cpi.GetRetType().TypeAsBaseType);
534 ilgenGet.Emit(OpCodes.Ret);
535 ilgenGet.DoEmit();
536
537 // dispatch methods
538 foreach (var mw in methods)
539 EmitDispatch(context, args, tb, mw, implParameters, implMethod, instantiatedMethodType, capturedFields);
540
541 // writeReplace method
542 if (serializable)
543 {
544 var writeReplace = tb.DefineMethod("writeReplace", MethodAttributes.Private, context.Context.Types.Object, Type.EmptyTypes);
545 ilgen = context.Context.CodeEmitterFactory.Create(writeReplace);
546 context.TypeWrapper.EmitClassLiteral(ilgen);
547 ilgen.Emit(OpCodes.Ldstr, cpi.GetRetType().Name.Replace('.', '/'));
548 ilgen.Emit(OpCodes.Ldstr, cpi.Name);
549 ilgen.Emit(OpCodes.Ldstr, samMethodType.Signature.Replace('.', '/'));
550 ilgen.EmitLdc_I4((int)implMethod.Kind);
551 ilgen.Emit(OpCodes.Ldstr, implMethod.Class.Replace('.', '/'));
552 ilgen.Emit(OpCodes.Ldstr, implMethod.Name);
553 ilgen.Emit(OpCodes.Ldstr, implMethod.Signature.Replace('.', '/'));
554 ilgen.Emit(OpCodes.Ldstr, instantiatedMethodType.Signature.Replace('.', '/'));
555 ilgen.EmitLdc_I4(capturedFields.Length);
556 ilgen.Emit(OpCodes.Newarr, context.Context.Types.Object);
557 for (int i = 0; i < capturedFields.Length; i++)
558 {
559 ilgen.Emit(OpCodes.Dup);
560 ilgen.EmitLdc_I4(i);
561 ilgen.EmitLdarg(0);
562 ilgen.Emit(OpCodes.Ldfld, capturedFields[i]);
563 if (args[i].IsPrimitive)
564 {
565 context.Context.Boxer.EmitBox(ilgen, args[i]);
566 }
567 else if (args[i].IsGhost)
568 {
569 args[i].EmitConvSignatureTypeToStackType(ilgen);
570 }
571 ilgen.Emit(OpCodes.Stelem, context.Context.Types.Object);
572 }
573 RuntimeJavaMethod ctorSerializedLambda = context.Context.ClassLoaderFactory.LoadClassCritical("java.lang.invoke.SerializedLambda").GetMethod(StringConstants.INIT,
574 "(Ljava.lang.Class;Ljava.lang.String;Ljava.lang.String;Ljava.lang.String;ILjava.lang.String;Ljava.lang.String;Ljava.lang.String;Ljava.lang.String;[Ljava.lang.Object;)V", false);
575 ctorSerializedLambda.Link();
576 ctorSerializedLambda.EmitNewobj(ilgen);
577 ilgen.Emit(OpCodes.Ret);
578 ilgen.DoEmit();
579
580 if (!context.TypeWrapper.ClassLoader.NoAutomagicSerialization)
581 {
582 // add .NET serialization interop support
583 context.Context.Serialization.MarkSerializable(tb);
584 context.Context.Serialization.AddGetObjectData(tb);
585 }
586 }
587
588 return getInstance;
589 }
590
603 static void EmitDispatch(RuntimeByteCodeJavaType.FinishContext context, RuntimeJavaType[] args, TypeBuilder tb, RuntimeJavaMethod interfaceMethod, RuntimeJavaType[] implParameters, ClassFile.ConstantPoolItemMethodHandle implMethod, ClassFile.ConstantPoolItemMethodType instantiatedMethodType, FieldBuilder[] capturedFields)
604 {
605 var mb = interfaceMethod.GetDefineMethodHelper().DefineMethod(context.TypeWrapper, tb, interfaceMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Final);
606 if (interfaceMethod.Name != interfaceMethod.RealName)
607 tb.DefineMethodOverride(mb, (MethodInfo)interfaceMethod.GetMethod());
608
609 context.Context.AttributeHelper.HideFromJava(mb);
610
611 var ilgen = context.Context.CodeEmitterFactory.Create(mb);
612 for (int i = 0; i < capturedFields.Length; i++)
613 {
614 ilgen.EmitLdarg(0);
615 var opc = OpCodes.Ldfld;
616 if (i == 0 && args[0].IsGhost)
617 {
618 switch (implMethod.Kind)
619 {
620 case MethodHandleKind.InvokeInterface:
621 case MethodHandleKind.InvokeVirtual:
622 case MethodHandleKind.InvokeSpecial:
623 opc = OpCodes.Ldflda;
624 break;
625 }
626 }
627
628 ilgen.Emit(opc, capturedFields[i]);
629 }
630
631 // emit conversions for interface arguments
632 // Ui represents the type on the interface, which for generics may be erased to Object
633 // Ti represents the incoming type on the interface, which usually matches Ui, but may not in the case of generics
634 // Aj represents the outgoing type on the implementation, to which we need to convert the argument to before calling
635 for (int i = 0, count = interfaceMethod.GetParameters().Length, k = capturedFields.Length; i < count; i++)
636 {
637 ilgen.EmitLdarg(i + 1);
638 var Ui = interfaceMethod.GetParameters()[i];
639 var Ti = instantiatedMethodType.GetArgTypes()[i];
640 var Aj = implParameters[i + k];
641
642 // incoming byte arguments are first converted to integers
643 if (Ui == context.Context.PrimitiveJavaTypeFactory.BYTE)
644 ilgen.Emit(OpCodes.Conv_I1);
645
646 // instantiation type does not equal interface type
647 if (Ti != Ui)
648 {
649 if (Ti.IsGhost)
650 {
651 Ti.EmitConvStackTypeToSignatureType(ilgen, Ui);
652 }
653 else if (Ui.IsGhost)
654 {
655 Ui.EmitConvSignatureTypeToStackType(ilgen);
656 }
657 else
658 {
659 Ti.EmitCheckcast(ilgen);
660 }
661 }
662
663 // implementation type does not equal instantiation type
664 if (Aj != Ti)
665 {
666 if (Ti.IsPrimitive && !Aj.IsPrimitive)
667 {
668 // box primitive
669 context.Context.Boxer.EmitBox(ilgen, Ti);
670 }
671 else if (!Ti.IsPrimitive && Aj.IsPrimitive)
672 {
673 // unbox primitive
674 var primitive = GetUnboxedPrimitiveType(Ti);
675 context.Context.Boxer.EmitUnbox(ilgen, primitive, false);
676 if (primitive == context.Context.PrimitiveJavaTypeFactory.BYTE)
677 ilgen.Emit(OpCodes.Conv_I1);
678 }
679 else if (Aj == context.Context.PrimitiveJavaTypeFactory.LONG)
680 {
681 // long is i8
682 ilgen.Emit(OpCodes.Conv_I8);
683 }
684 else if (Aj == context.Context.PrimitiveJavaTypeFactory.FLOAT)
685 {
686 // float is r4
687 ilgen.Emit(OpCodes.Conv_R4);
688 }
689 else if (Aj == context.Context.PrimitiveJavaTypeFactory.DOUBLE)
690 {
691 // double is r8
692 ilgen.Emit(OpCodes.Conv_R8);
693 }
694 else if (Aj.IsGhost)
695 {
696 // convert to ghost type
697 Aj.EmitConvStackTypeToSignatureType(ilgen, Ti);
698 }
699 }
700 }
701
702 switch (implMethod.Kind)
703 {
704 case MethodHandleKind.InvokeVirtual:
705 case MethodHandleKind.InvokeInterface:
706 ((RuntimeJavaMethod)implMethod.Member).EmitCallvirt(ilgen);
707 break;
708 case MethodHandleKind.NewInvokeSpecial:
709 ((RuntimeJavaMethod)implMethod.Member).EmitNewobj(ilgen);
710 break;
711 case MethodHandleKind.InvokeStatic:
712 case MethodHandleKind.InvokeSpecial:
713 ((RuntimeJavaMethod)implMethod.Member).EmitCall(ilgen);
714 break;
715 default:
716 throw new InvalidOperationException();
717 }
718
719 var Ru = interfaceMethod.ReturnType;
720 var Ra = GetImplReturnType(implMethod);
721 var Rt = instantiatedMethodType.GetRetType();
722
723 if (Ra == context.Context.PrimitiveJavaTypeFactory.BYTE)
724 {
725 ilgen.Emit(OpCodes.Conv_I1);
726 }
727
728 if (Ra != Ru)
729 {
730 if (Ru == context.Context.PrimitiveJavaTypeFactory.VOID)
731 {
732 ilgen.Emit(OpCodes.Pop);
733 }
734 else if (Ra.IsGhost)
735 {
736 Ra.EmitConvSignatureTypeToStackType(ilgen);
737 }
738 else if (Ru.IsGhost)
739 {
740 Ru.EmitConvStackTypeToSignatureType(ilgen, Ra);
741 }
742 }
743
744 if (Ra != Rt)
745 {
746 if (Rt.IsPrimitive)
747 {
748 if (Rt == context.Context.PrimitiveJavaTypeFactory.VOID)
749 {
750 // already popped
751 }
752 else if (!Ra.IsPrimitive)
753 {
754 var primitive = GetUnboxedPrimitiveType(Ra);
755 if (primitive != null)
756 {
757 context.Context.Boxer.EmitUnbox(ilgen, primitive, false);
758 }
759 else
760 {
761 // If Q is not a primitive wrapper, cast Q to the base Wrapper(S); for example Number for numeric types
762 EmitConvertingUnbox(ilgen, Rt);
763 }
764 }
765 else if (Rt == context.Context.PrimitiveJavaTypeFactory.LONG)
766 {
767 ilgen.Emit(OpCodes.Conv_I8);
768 }
769 else if (Rt == context.Context.PrimitiveJavaTypeFactory.FLOAT)
770 {
771 ilgen.Emit(OpCodes.Conv_R4);
772 }
773 else if (Rt == context.Context.PrimitiveJavaTypeFactory.DOUBLE)
774 {
775 ilgen.Emit(OpCodes.Conv_R8);
776 }
777 }
778 else if (Ra.IsPrimitive)
779 {
780 var tw = GetUnboxedPrimitiveType(Rt);
781 if (tw == null)
782 tw = Ra;
783
784 context.Context.Boxer.EmitBox(ilgen, tw);
785 }
786 else
787 {
788 Rt.EmitCheckcast(ilgen);
789 }
790 }
791 ilgen.EmitTailCallPrevention();
792 ilgen.Emit(OpCodes.Ret);
793 ilgen.DoEmit();
794 }
795
796 static void EmitConvertingUnbox(CodeEmitter ilgen, RuntimeJavaType tw)
797 {
798 switch (tw.SigName[0])
799 {
800 case 'Z':
801 case 'C':
802 tw.Context.Boxer.EmitUnbox(ilgen, tw, true);
803 break;
804 case 'B':
805 EmitUnboxNumber(tw.Context, ilgen, "byteValue", "()B");
806 break;
807 case 'S':
808 EmitUnboxNumber(tw.Context, ilgen, "shortValue", "()S");
809 break;
810 case 'I':
811 EmitUnboxNumber(tw.Context, ilgen, "intValue", "()I");
812 break;
813 case 'J':
814 EmitUnboxNumber(tw.Context, ilgen, "longValue", "()J");
815 break;
816 case 'F':
817 EmitUnboxNumber(tw.Context, ilgen, "floatValue", "()F");
818 break;
819 case 'D':
820 EmitUnboxNumber(tw.Context, ilgen, "doubleValue", "()D");
821 break;
822 default:
823 throw new InvalidOperationException();
824 }
825 }
826
827 static void EmitUnboxNumber(RuntimeContext context, CodeEmitter ilgen, string methodName, string methodSig)
828 {
829 var tw = context.ClassLoaderFactory.LoadClassCritical("java.lang.Number");
830 tw.EmitCheckcast(ilgen);
831 var mw = tw.GetMethod(methodName, methodSig, false);
832 mw.Link();
833 mw.EmitCallvirt(ilgen);
834 }
835
836 static void AddDefaultInterfaceMethods(RuntimeByteCodeJavaType.FinishContext context, RuntimeJavaMethod[] methodList, TypeBuilder tb)
837 {
838 // we use special name to hide these from Java reflection
839 const MethodAttributes attr = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Final | MethodAttributes.SpecialName;
840
841 var factory = context.TypeWrapper.ClassLoader.GetTypeWrapperFactory();
842 foreach (var mw in methodList)
843 {
844 if (!mw.IsAbstract)
845 {
846 var mb = mw.GetDefineMethodHelper().DefineMethod(factory, tb, mw.Name, attr);
847 if (mw.Name != mw.RealName)
848 tb.DefineMethodOverride(mb, (MethodInfo)mw.GetMethod());
849
850 context.EmitCallDefaultInterfaceMethod(mb, mw);
851 }
852 else if (IsObjectMethod(mw))
853 {
854 var mb = mw.GetDefineMethodHelper().DefineMethod(factory, tb, mw.Name, attr);
855 if (mw.Name != mw.RealName)
856 tb.DefineMethodOverride(mb, (MethodInfo)mw.GetMethod());
857
858 var ilgen = context.Context.CodeEmitterFactory.Create(mb);
859 for (int i = 0, count = mw.GetParameters().Length; i <= count; i++)
860 ilgen.EmitLdarg(i);
861
862 context.Context.JavaBase.TypeOfJavaLangObject.GetMethod(mw.Name, mw.Signature, false).EmitCallvirt(ilgen);
863 ilgen.Emit(OpCodes.Ret);
864 ilgen.DoEmit();
865 }
866 }
867 }
868
869 static bool IsSupportedImplMethod(ClassFile.ConstantPoolItemMethodHandle implMethod, RuntimeJavaType caller, RuntimeJavaType[] captured, ClassFile.ConstantPoolItemMethodType instantiatedMethodType)
870 {
871 switch (implMethod.Kind)
872 {
873 case MethodHandleKind.InvokeVirtual:
874 case MethodHandleKind.InvokeInterface:
875 case MethodHandleKind.NewInvokeSpecial:
876 case MethodHandleKind.InvokeStatic:
877 case MethodHandleKind.InvokeSpecial:
878 break;
879 default:
880 return false;
881 }
882
883 var mw = (RuntimeJavaMethod)implMethod.Member;
884 if (mw == null || mw.HasCallerID || RuntimeByteCodeJavaType.RequiresDynamicReflectionCallerClass(mw.DeclaringType.Name, mw.Name, mw.Signature))
885 return false;
886
887 RuntimeJavaType instance;
888 if (mw.IsConstructor)
889 {
890 instance = mw.DeclaringType;
891 }
892 else if (mw.IsStatic)
893 {
894 instance = null;
895 }
896 else
897 {
898 // if implMethod is an instance method, the type of the first captured value must be subtype of implMethod.DeclaringType
899 instance = captured.Length == 0 ? instantiatedMethodType.GetArgTypes()[0] : captured[0];
900 if (!instance.IsAssignableTo(mw.DeclaringType))
901 return false;
902 }
903
904 if (!mw.IsAccessibleFrom(mw.DeclaringType, caller, instance))
905 return false;
906
907 mw.Link();
908 return true;
909 }
910
911 static bool IsSupportedInterface(RuntimeJavaType tw, RuntimeJavaType caller)
912 {
913 return tw.IsInterface && !tw.IsGhost && tw.IsAccessibleFrom(caller) && !tw.Context.Serialization.IsISerializable(tw);
914 }
915
916 static bool CheckSupportedInterfaces(RuntimeJavaType caller, RuntimeJavaType tw, RuntimeJavaType[] markers, ClassFile.ConstantPoolItemMethodType[] bridges, out RuntimeJavaMethod[] methodList)
917 {
918 // we don't need to check for unloadable, because we already did that while validating the invoke signature
919 if (!IsSupportedInterface(tw, caller))
920 {
921 methodList = null;
922 return false;
923 }
924
925 Dictionary<MethodKey, RuntimeJavaMethod> methods = new Dictionary<MethodKey, RuntimeJavaMethod>();
926 int abstractMethodCount = 0;
927 int bridgeMethodCount = 0;
928 if (GatherAllInterfaceMethods(tw, bridges, methods, ref abstractMethodCount, ref bridgeMethodCount) && abstractMethodCount == 1)
929 {
930 foreach (var marker in markers)
931 {
932 if (!IsSupportedInterface(marker, caller))
933 {
934 methodList = null;
935 return false;
936 }
937
938 if (!GatherAllInterfaceMethods(marker, null, methods, ref abstractMethodCount, ref bridgeMethodCount) || abstractMethodCount != 1)
939 {
940 methodList = null;
941 return false;
942 }
943 }
944
945 if (bridges != null && bridgeMethodCount != bridges.Length)
946 {
947 methodList = null;
948 return false;
949 }
950
951 methodList = new RuntimeJavaMethod[methods.Count];
952 methods.Values.CopyTo(methodList, 0);
953 return true;
954 }
955
956 methodList = null;
957 return false;
958 }
959
960 static bool GatherAllInterfaceMethods(RuntimeJavaType tw, ClassFile.ConstantPoolItemMethodType[] bridges, Dictionary<MethodKey, RuntimeJavaMethod> methods, ref int abstractMethodCount, ref int bridgeMethodCount)
961 {
962 foreach (var mw in tw.GetMethods())
963 {
964 if (mw.IsVirtual)
965 {
966 if (mw is RuntimeMirandaJavaMethod mmw)
967 {
968 if (mmw.Error != null)
969 return false;
970
971 continue;
972 }
973
974 var key = new MethodKey("", mw.Name, mw.Signature);
975 if (methods.TryGetValue(key, out var current))
976 {
977 if (!MatchSignatures(mw, current))
978 {
979 // linkage error (or unloadable type)
980 return false;
981 }
982 }
983 else
984 {
985 methods.Add(key, mw);
986
987 if (mw.IsAbstract && !IsObjectMethod(mw))
988 {
989 if (bridges != null && IsBridge(mw, bridges))
990 bridgeMethodCount++;
991 else
992 abstractMethodCount++;
993 }
994 }
995
996 mw.Link();
997 if (mw.GetMethod() == null)
998 return false;
999
1000 if (current != null && mw.RealName != current.RealName)
1001 return false;
1002 }
1003 }
1004
1005 foreach (var tw1 in tw.Interfaces)
1006 if (!GatherAllInterfaceMethods(tw1, bridges, methods, ref abstractMethodCount, ref bridgeMethodCount))
1007 return false;
1008
1009 return true;
1010 }
1011
1012 static bool IsBridge(RuntimeJavaMethod mw, ClassFile.ConstantPoolItemMethodType[] bridges)
1013 {
1014 foreach (ClassFile.ConstantPoolItemMethodType bridge in bridges)
1015 if (bridge.Signature == mw.Signature)
1016 return true;
1017
1018 return false;
1019 }
1020
1021 static bool IsObjectMethod(RuntimeJavaMethod mw)
1022 {
1023 RuntimeJavaMethod objectMethod;
1024 return (objectMethod = mw.DeclaringType.Context.JavaBase.TypeOfJavaLangObject.GetMethod(mw.Name, mw.Signature, false)) != null && objectMethod.IsPublic;
1025 }
1026
1027 static bool MatchSignatures(RuntimeJavaMethod interfaceMethod, ClassFile.ConstantPoolItemMethodType samMethodType)
1028 {
1029 return interfaceMethod.ReturnType == samMethodType.GetRetType() && MatchTypes(interfaceMethod.GetParameters(), samMethodType.GetArgTypes());
1030 }
1031
1032 static bool MatchSignatures(RuntimeJavaMethod mw1, RuntimeJavaMethod mw2)
1033 {
1034 mw1.Link();
1035 mw2.Link();
1036 return mw1.ReturnType == mw2.ReturnType && MatchTypes(mw1.GetParameters(), mw2.GetParameters());
1037 }
1038
1039 static bool MatchTypes(RuntimeJavaType[] ar1, RuntimeJavaType[] ar2)
1040 {
1041 if (ar1.Length != ar2.Length)
1042 return false;
1043
1044 for (int i = 0; i < ar1.Length; i++)
1045 if (ar1[i] != ar2[i])
1046 return false;
1047
1048 return true;
1049 }
1050
1057 static bool IsLambdaMetafactory(ClassFile classFile, ClassFile.BootstrapMethod bsm)
1058 {
1059 return bsm.ArgumentCount == 3 &&
1060 classFile.GetConstantPoolConstantType(bsm.GetArgument(0)) == ClassFile.ConstantType.MethodType &&
1061 classFile.GetConstantPoolConstantType(bsm.GetArgument(1)) == ClassFile.ConstantType.MethodHandle &&
1062 classFile.GetConstantPoolConstantType(bsm.GetArgument(2)) == ClassFile.ConstantType.MethodType &&
1063 classFile.GetConstantPoolConstantMethodHandle(bsm.BootstrapMethodIndex) is { Kind: MethodHandleKind.InvokeStatic, Member: not null } mh &&
1064 IsLambdaMetafactory(mh.Member);
1065 }
1066
1072 static bool IsLambdaMetafactory(RuntimeJavaMember mw)
1073 {
1074 return mw.Name == "metafactory"
1075 && mw.Signature == "(Ljava.lang.invoke.MethodHandles$Lookup;Ljava.lang.String;Ljava.lang.invoke.MethodType;Ljava.lang.invoke.MethodType;Ljava.lang.invoke.MethodHandle;Ljava.lang.invoke.MethodType;)Ljava.lang.invoke.CallSite;"
1076 && mw.DeclaringType.Name == "java.lang.invoke.LambdaMetafactory";
1077 }
1078
1079 [Flags]
1080 enum AltFlags
1081 {
1082 Serializable = 1,
1083 Markers = 2,
1084 Bridges = 4,
1085 Mask = Serializable | Markers | Bridges
1086 }
1087
1088 private static bool IsLambdaAltMetafactory(ClassFile classFile, ClassFile.BootstrapMethod bsm)
1089 {
1090 ClassFile.ConstantPoolItemMethodHandle mh;
1091 AltFlags flags;
1092 int argpos = 4;
1093 return bsm.ArgumentCount >= 4
1094 && (mh = classFile.GetConstantPoolConstantMethodHandle(bsm.BootstrapMethodIndex)).Kind == MethodHandleKind.InvokeStatic
1095 && mh.Member != null
1096 && IsLambdaAltMetafactory(mh.Member)
1097 && classFile.GetConstantPoolConstantType(bsm.GetArgument(0)) == ClassFile.ConstantType.MethodType
1098 && classFile.GetConstantPoolConstantType(bsm.GetArgument(1)) == ClassFile.ConstantType.MethodHandle
1099 && classFile.GetConstantPoolConstantType(bsm.GetArgument(2)) == ClassFile.ConstantType.MethodType
1100 && classFile.GetConstantPoolConstantType(bsm.GetArgument(3)) == ClassFile.ConstantType.Integer
1101 && ((flags = (AltFlags)classFile.GetConstantPoolConstantInteger((IntegerConstantHandle)bsm.GetArgument(3))) & ~AltFlags.Mask) == 0
1102 && ((flags & AltFlags.Markers) == 0 || CheckOptionalArgs(classFile, bsm, ClassFile.ConstantType.Class, ref argpos))
1103 && ((flags & AltFlags.Bridges) == 0 || CheckOptionalArgs(classFile, bsm, ClassFile.ConstantType.MethodType, ref argpos))
1104 && argpos == bsm.ArgumentCount;
1105 }
1106
1107 static bool IsLambdaAltMetafactory(RuntimeJavaMember mw)
1108 {
1109 return mw.Name == "altMetafactory"
1110 && mw.Signature == "(Ljava.lang.invoke.MethodHandles$Lookup;Ljava.lang.String;Ljava.lang.invoke.MethodType;[Ljava.lang.Object;)Ljava.lang.invoke.CallSite;"
1111 && mw.DeclaringType.Name == "java.lang.invoke.LambdaMetafactory";
1112 }
1113
1114 static bool CheckOptionalArgs(ClassFile classFile, ClassFile.BootstrapMethod bsm, ClassFile.ConstantType type, ref int argpos)
1115 {
1116 if (bsm.ArgumentCount - argpos < 1)
1117 return false;
1118
1119 if (classFile.GetConstantPoolConstantType(bsm.GetArgument(argpos)) != ClassFile.ConstantType.Integer)
1120 return false;
1121
1122 int count = classFile.GetConstantPoolConstantInteger((IntegerConstantHandle)bsm.GetArgument(argpos++));
1123 if (count < 0 || bsm.ArgumentCount - argpos < count)
1124 return false;
1125
1126 for (int i = 0; i < count; i++)
1127 if (classFile.GetConstantPoolConstantType(bsm.GetArgument(argpos++)) != type)
1128 return false;
1129
1130 return true;
1131 }
1132
1133 private static bool HasUnloadable(ClassFile.ConstantPoolItemInvokeDynamic cpi)
1134 {
1135 return HasUnloadable(cpi.GetArgTypes()) || cpi.GetRetType().IsUnloadable;
1136 }
1137
1138 private static bool HasUnloadable(ClassFile.ConstantPoolItemMethodType cpi)
1139 {
1140 return HasUnloadable(cpi.GetArgTypes()) || cpi.GetRetType().IsUnloadable;
1141 }
1142
1143 private static bool HasUnloadable(ClassFile.ConstantPoolItemMI cpi)
1144 {
1145 return HasUnloadable(cpi.GetArgTypes()) || cpi.GetRetType().IsUnloadable;
1146 }
1147
1148 private static bool HasUnloadable(RuntimeJavaType[] wrappers)
1149 {
1150 foreach (RuntimeJavaType tw in wrappers)
1151 {
1152 if (tw.IsUnloadable)
1153 {
1154 return true;
1155 }
1156 }
1157 return false;
1158 }
1159 }
1160
1161}
IKVM.Reflection.Type Type
IKVM.Reflection.MethodInfo MethodInfo
CodeEmitter Create(MethodBuilder mb)
Creates a new instance.
RuntimeContext Context
Gets the RuntimeContext that hosts this code emitter.
IL based implementation of InvokeDynamic. Supports LambdaMetafactory specific boot strap methods.
Maintains services relevant to an instane of the IKVM runtime.
CodeEmitterFactory CodeEmitterFactory
Gets the CodeEmitterFactory associated with this instance of the runtime.
Serialization Serialization
Gets the Serialization associated with this instance of the runtime.
Boxer Boxer
Gets the Boxer associated with this instance of the runtime.
RuntimePrimitiveJavaTypeFactory PrimitiveJavaTypeFactory
Gets the RuntimePrimitiveJavaTypeFactory associated with this instance of the runtime.
CoreClasses JavaBase
Gets the CoreClasses associated with this instance of the runtime.
RuntimeClassLoaderFactory ClassLoaderFactory
Gets the RuntimeClassLoaderFactory associated with this instance of the runtime.
record struct MethodKey(string ClassName, string MethodName, string MethodSig)