From e552d41d5711c8a1d8f581e1416c45fa7aabd1e1 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Wed, 25 Dec 2024 10:30:15 +0100 Subject: [PATCH 01/12] Replace opField with opMethod for lambdas. --- .../AbstractValidatingLambdaMetafactory.java | 18 ++++----- .../invoke/InnerClassLambdaMetafactory.java | 20 +++++----- .../java/lang/invoke/LambdaMetafactory.java | 7 ++-- .../sun/tools/javac/comp/LambdaToMethod.java | 4 +- .../share/classes/jdk/incubator/code/Op.java | 2 +- .../code/bytecode/BytecodeGenerator.java | 21 ++++++---- .../incubator/code/internal/QuotedHelper.java | 3 ++ .../code/internal/ReflectMethods.java | 38 ++++++------------- 8 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java index 55ef41718fc..6b158da6509 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -69,10 +69,9 @@ final boolean isSerializable; // Should the returned instance be serializable final Class[] altInterfaces; // Additional interfaces to be implemented final MethodType[] altMethods; // Signatures of additional methods to bridge - final MethodHandle quotableOpField; // A getter method handle that is used to retrieve the - // string representation of the quotable lambda's associated - // intermediate representation (can be null). - final MethodHandleInfo quotableOpFieldInfo; // Info about the quotable getter method handle (can be null). + final MethodHandle quotableOpGetter; // A getter method handle that is used to retrieve the + // the quotable lambda's associated intermediate representation (can be null). + final MethodHandleInfo quotableOpGetterInfo; // Info about the quotable getter method handle (can be null). /** * Meta-factory constructor. @@ -187,7 +186,7 @@ this.isSerializable = isSerializable; this.altInterfaces = altInterfaces; this.altMethods = altMethods; - this.quotableOpField = reflectiveField; + this.quotableOpGetter = reflectiveField; if (interfaceMethodName.isEmpty() || interfaceMethodName.indexOf('.') >= 0 || @@ -217,16 +216,15 @@ if (reflectiveField != null) { try { - quotableOpFieldInfo = caller.revealDirect(reflectiveField); // may throw SecurityException + quotableOpGetterInfo = caller.revealDirect(reflectiveField); // may throw SecurityException } catch (IllegalArgumentException e) { throw new LambdaConversionException(implementation + " is not direct or cannot be cracked"); } - if (quotableOpFieldInfo.getReferenceKind() != REF_getField && - quotableOpFieldInfo.getReferenceKind() != REF_getStatic) { - throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", quotableOpFieldInfo)); + if (quotableOpGetterInfo.getReferenceKind() != REF_invokeStatic) { + throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", quotableOpGetterInfo)); } } else { - quotableOpFieldInfo = null; + quotableOpGetterInfo = null; } } diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index bdca3633f14..2350d75991c 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -336,7 +336,7 @@ public void accept(ClassBuilder clb) { } // if quotable, generate the field that will hold the value of quoted - if (quotableOpField != null) { + if (quotableOpGetter != null) { clb.withField(quotedInstanceFieldName, CodeReflectionSupport.CD_Quoted, ACC_PRIVATE + ACC_FINAL); } @@ -367,7 +367,7 @@ public void accept(ClassBuilder clb) { else if (finalAccidentallySerializable) generateSerializationHostileMethods(clb); - if (quotableOpField != null) { + if (quotableOpGetter != null) { generateQuotableMethod(clb); } } @@ -378,10 +378,10 @@ else if (finalAccidentallySerializable) try { // this class is linked at the indy callsite; so define a hidden nestmate List classdata; - if (useImplMethodHandle || quotableOpField != null) { - classdata = quotableOpField == null ? + if (useImplMethodHandle || quotableOpGetter != null) { + classdata = quotableOpGetter == null ? List.of(implementation) : - List.of(implementation, quotableOpField, CodeReflectionSupport.HANDLE_MAKE_QUOTED); + List.of(implementation, quotableOpGetter, CodeReflectionSupport.HANDLE_MAKE_QUOTED); } else { classdata = null; } @@ -434,7 +434,7 @@ public void accept(CodeBuilder cob) { cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i)); cob.putfield(pool.fieldRefEntry(lambdaClassEntry, pool.nameAndTypeEntry(argNames[i], argDescs[i]))); } - if (quotableOpField != null) { + if (quotableOpGetter != null) { generateQuotedFieldInitializer(cob); } cob.return_(); @@ -451,8 +451,8 @@ private void generateQuotedFieldInitializer(CodeBuilder cob) { .ldc(cp.constantDynamicEntry(cp.bsmEntry(bsmDataAt, List.of(cp.intEntry(2))), natMH)) // load op string from field .ldc(cp.constantDynamicEntry(cp.bsmEntry(bsmDataAt, List.of(cp.intEntry(1))), natMH)); - MethodType mtype = quotableOpFieldInfo.getMethodType(); - if (quotableOpFieldInfo.getReferenceKind() != MethodHandleInfo.REF_getStatic) { + MethodType mtype = quotableOpGetterInfo.getMethodType(); + if (quotableOpGetterInfo.getReferenceKind() != MethodHandleInfo.REF_invokeStatic) { mtype = mtype.insertParameterTypes(0, implClass); } cob.invokevirtual(CD_MethodHandle, "invokeExact", mtype.describeConstable().get()); @@ -492,8 +492,10 @@ static class CodeReflectionSupport { .loadClass("jdk.incubator.code.Quotable"); Class quotedHelper = layer.findLoader("jdk.incubator.code") .loadClass("jdk.incubator.code.internal.QuotedHelper"); + Class funcOp = layer.findLoader("jdk.incubator.code") + .loadClass("jdk.incubator.code.op.CoreOp$FuncOp"); MethodHandle makeQuoted = Lookup.IMPL_LOOKUP.findStatic(quotedHelper, "makeQuoted", - MethodType.methodType(QUOTED_CLASS, MethodHandles.Lookup.class, String.class, Object[].class)); + MethodType.methodType(QUOTED_CLASS, MethodHandles.Lookup.class, funcOp, Object[].class)); HANDLE_MAKE_QUOTED = makeQuoted.bindTo(Lookup.IMPL_LOOKUP); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java index 74582398f55..9ab785b9977 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java @@ -500,7 +500,8 @@ public static CallSite altMetafactory(MethodHandles.Lookup caller, int flags = extractArg(args, argIndex++, Integer.class); Class[] altInterfaces = EMPTY_CLASS_ARRAY; MethodType[] altMethods = EMPTY_MT_ARRAY; - MethodHandle quotableField = null; + // Getter that returns the op of a Quotable instance + MethodHandle quotableOpGetter = null; if ((flags & FLAG_MARKERS) != 0) { int altInterfaceCount = extractArg(args, argIndex++, Integer.class); if (altInterfaceCount < 0) { @@ -522,7 +523,7 @@ public static CallSite altMetafactory(MethodHandles.Lookup caller, } } if ((flags & FLAG_QUOTABLE) != 0) { - quotableField = extractArg(args, argIndex++, MethodHandle.class); + quotableOpGetter = extractArg(args, argIndex++, MethodHandle.class); altInterfaces = Arrays.copyOf(altInterfaces, altInterfaces.length + 1); altInterfaces[altInterfaces.length-1] = InnerClassLambdaMetafactory.CodeReflectionSupport.QUOTABLE_CLASS; } @@ -551,7 +552,7 @@ public static CallSite altMetafactory(MethodHandles.Lookup caller, isSerializable, altInterfaces, altMethods, - quotableField); + quotableOpGetter); mf.validateMetafactoryArgs(); return mf.buildCallSite(); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index 8eff9ee31b9..9dc30d35f6a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -879,8 +879,8 @@ private JCExpression makeMetafactoryIndyCall(JCFunctionalExpression tree, } } if (isQuotable) { - VarSymbol reflectField = (VarSymbol)tree.codeModel; - staticArgs = staticArgs.append(reflectField.asMethodHandle(true)); + MethodSymbol opMethodSym = (MethodSymbol)tree.codeModel; + staticArgs = staticArgs.append(opMethodSym.asHandle()); } if (isSerializable) { int prevPos = make.pos; diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index 0facc946f3f..8b923d8e3cc 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -494,7 +494,7 @@ private static Optional createCodeModel(Method method) { case '.', ';', '[', '/': sig[i] = '$'; } } - String opMethodName = "method$op$" + new String(sig); + String opMethodName = "op$" + new String(sig); Method opMethod; try { // @@@ Use method handle with full power mode diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/BytecodeGenerator.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/BytecodeGenerator.java index 4d591356da6..5b3c526ca9f 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/BytecodeGenerator.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/bytecode/BytecodeGenerator.java @@ -54,6 +54,7 @@ import jdk.incubator.code.Value; import jdk.incubator.code.op.CoreOp; import jdk.incubator.code.op.CoreOp.*; +import jdk.incubator.code.parser.OpParser; import jdk.incubator.code.type.ArrayType; import jdk.incubator.code.type.FieldRef; import jdk.incubator.code.type.FunctionType; @@ -163,9 +164,15 @@ public static byte[] generateClassData(MethodHandl for (int i = 0; i < lambdaSink.size(); i++) { LambdaOp lop = lambdaSink.get(i); if (quotable.get(i)) { - clb.withField("lambda$" + i + "$op", CD_String, fb -> fb - .withFlags(ClassFile.ACC_STATIC) - .with(ConstantValueAttribute.of(quote(lop).toText()))); + // return (FuncOp) OpParser.fromOpString(opText) + clb.withMethod("op$lambda$" + i, MethodTypeDesc.of(FuncOp.class.describeConstable().get()), + ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC | ClassFile.ACC_SYNTHETIC, mb -> mb.withCode(cb -> cb + .loadConstant(quote(lop).toText()) + .invoke(Opcode.INVOKESTATIC, OpParser.class.describeConstable().get(), + "fromStringOfFuncOp", + MethodTypeDesc.of(Op.class.describeConstable().get(), CD_String), false) + .checkcast(FuncOp.class.describeConstable().get()) + .areturn())); } generateMethod(lookup, className, "lambda$" + i, lop, clb, lambdaSink, quotable); } @@ -891,10 +898,10 @@ private void generate() { mtd.insertParameterTypes(0, captureTypes)), mtd, LambdaMetafactory.FLAG_QUOTABLE, - MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.STATIC_GETTER, - className, - "lambda$" + lambdaIndex + "$op", - CD_String))); + MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC, + className, + "op$lambda$" + lambdaIndex, + MethodTypeDesc.of(FuncOp.class.describeConstable().get())))); quotable.set(lambdaSink.size()); } else { cob.invokedynamic(DynamicCallSiteDesc.of( diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java index 42df54a40e1..4e626bd71bd 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java @@ -37,4 +37,7 @@ public static Quoted makeQuoted(MethodHandles.Lookup lookup, String opText, Obje FuncOp op = (FuncOp)OpParser.fromStringOfFuncOp(opText); return (Quoted)Interpreter.invoke(lookup, op, args); } + public static Quoted makeQuoted(MethodHandles.Lookup lookup, FuncOp op, Object[] args) { + return (Quoted)Interpreter.invoke(lookup, op, args); + } } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java index 8b67add25d6..754315bb63b 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/ReflectMethods.java @@ -253,24 +253,23 @@ public void visitLambda(JCLambda tree) { // dump the method IR if requested log.note(QuotedIrDump(funcOp.toText())); } - // create a static final field holding the op' string text. - // The name of the field is foo$op, where 'foo' is the name of the corresponding method. - JCVariableDecl opField = opFieldDecl(lambdaName(), 0, funcOp); - classOps.add(opField); + // create a static method that returns the FuncOp representing the lambda + JCMethodDecl opMethod = opMethodDecl(lambdaName(), funcOp); + classOps.add(opMethod); switch (kind) { case QUOTED_STRUCTURAL -> { // @@@ Consider replacing with invokedynamic to quoted bootstrap method // Thereby we avoid certain dependencies and hide specific details - JCIdent opFieldId = make.Ident(opField.sym); + JCIdent opMethodId = make.Ident(opMethod.sym); ListBuffer interpreterArgs = new ListBuffer<>(); // Obtain MethodHandles.lookup() // @@@ Could probably use MethodHandles.publicLookup() JCMethodInvocation lookup = make.App(make.Ident(crSyms.methodHandlesLookup), com.sun.tools.javac.util.List.nil()); interpreterArgs.append(lookup); // Deserialize the func operation - JCMethodInvocation parsedOp = make.App(make.Ident(crSyms.opParserFromString), com.sun.tools.javac.util.List.of(opFieldId)); - interpreterArgs.append(parsedOp); + JCMethodInvocation op = make.App(opMethodId); + interpreterArgs.append(op); // Append captured vars ListBuffer capturedArgs = quotedCapturedArgs(tree, bodyScanner); interpreterArgs.appendList(capturedArgs.toList()); @@ -283,7 +282,7 @@ public void visitLambda(JCLambda tree) { } case QUOTABLE -> { // leave the lambda in place, but also leave a trail for LambdaToMethod - tree.codeModel = opField.sym; + tree.codeModel = opMethod.sym; super.visitLambda(tree); } } @@ -315,11 +314,10 @@ public void visitReference(JCMemberReference tree) { // dump the method IR if requested log.note(QuotedIrDump(funcOp.toText())); } - // create a static final field holding the op' string text. - // The name of the field is foo$op, where 'foo' is the name of the corresponding method. - JCVariableDecl opField = opFieldDecl(lambdaName(), 0, funcOp); - classOps.add(opField); - tree.codeModel = opField.sym; + // create a method that returns the FuncOp representing the lambda + JCMethodDecl opMethod = opMethodDecl(lambdaName(), funcOp); + classOps.add(opMethod); + tree.codeModel = opMethod.sym; super.visitReference(tree); if (recvDecl != null) { result = copyReferenceWithReceiverVar(tree, recvDecl); @@ -390,22 +388,10 @@ Name methodName(MethodRef method) { return names.fromChars(sigCh, 0, sigCh.length); } - private JCVariableDecl opFieldDecl(Name prefix, long flags, CoreOp.FuncOp op) { - VarSymbol opFieldSym = new VarSymbol(flags | Flags.STATIC | Flags.FINAL | Flags.SYNTHETIC, - prefix.append('$', names.fromString("op")), - syms.stringType, - currentClassSym); - - currentClassSym.members().enter(opFieldSym); - JCLiteral opText = make.Literal(op.toText()); - JCVariableDecl opFieldTree = make.VarDef(opFieldSym, opText); - return opFieldTree; - } - private JCMethodDecl opMethodDecl(Name methodName, CoreOp.FuncOp op) { var mt = new MethodType(com.sun.tools.javac.util.List.nil(), crSyms.funcOpType, com.sun.tools.javac.util.List.nil(), syms.methodClass); - var mn = names.fromString("method$op$").append(methodName); + var mn = names.fromString("op$").append(methodName); var ms = new MethodSymbol(PUBLIC | STATIC | SYNTHETIC, mn, mt, currentClassSym); currentClassSym.members().enter(ms); From 40014139781d961ef5b527ce96078a43d3e7d898 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Tue, 31 Dec 2024 11:21:06 +0100 Subject: [PATCH 02/12] Make Quotable a marker interface --- .../invoke/InnerClassLambdaMetafactory.java | 14 +++------- .../share/classes/jdk/incubator/code/Op.java | 17 ++++++++++++ .../classes/jdk/incubator/code/Quotable.java | 5 +--- .../code/interpreter/Interpreter.java | 26 ++++++++++++++++--- .../jdk/java/lang/reflect/code/TestBuild.java | 2 +- .../java/lang/reflect/code/TestLambdaOps.java | 8 +++--- .../reflect/code/bytecode/TestBytecode.java | 6 ++--- .../bytecode/TestNestedCapturingLambda.java | 2 +- .../lang/reflect/code/linq/Queryable.java | 4 +-- .../code/stream/StreamFuserUsingQuotable.java | 16 ++++++------ .../javac/reflect/CodeReflectionTester.java | 2 +- .../javac/reflect/QuotedSameInstanceTest.java | 7 ++--- .../reflect/quoted/TestCaptureQuotable.java | 10 +++---- 13 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index 2350d75991c..a82c7a28b67 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -34,14 +34,12 @@ import java.lang.classfile.ClassBuilder; import java.lang.classfile.ClassFile; import java.lang.classfile.CodeBuilder; -import java.lang.classfile.FieldBuilder; import java.lang.classfile.MethodBuilder; import java.lang.classfile.Opcode; import java.lang.classfile.TypeKind; import java.lang.classfile.constantpool.MethodHandleEntry; import java.lang.classfile.constantpool.NameAndTypeEntry; import java.lang.constant.ClassDesc; -import java.lang.constant.DynamicConstantDesc; import java.lang.constant.MethodTypeDesc; import java.lang.invoke.MethodHandles.Lookup; import java.lang.module.Configuration; @@ -49,8 +47,6 @@ import java.lang.reflect.Modifier; import java.util.LinkedHashSet; import java.util.List; -import java.util.Optional; -import java.util.ServiceLoader; import java.util.Set; import java.util.function.Consumer; @@ -58,12 +54,10 @@ import java.lang.classfile.attribute.ExceptionsAttribute; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.ConstantPoolBuilder; -import java.lang.classfile.constantpool.MethodRefEntry; + import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.MethodHandleNatives.Constants.NESTMATE_CLASS; import static java.lang.invoke.MethodHandleNatives.Constants.STRONG_LOADER_LINK; -import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; -import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; import static java.lang.invoke.MethodType.methodType; import jdk.internal.constant.ConstantUtils; import jdk.internal.constant.MethodTypeDescImpl; @@ -368,7 +362,7 @@ else if (finalAccidentallySerializable) generateSerializationHostileMethods(clb); if (quotableOpGetter != null) { - generateQuotableMethod(clb); + generateQuotedMethod(clb); } } }); @@ -580,9 +574,9 @@ public void accept(CodeBuilder cob) { } /** - * Generate a writeReplace method that supports serialization + * Generate method #quoted() */ - private void generateQuotableMethod(ClassBuilder clb) { + private void generateQuotedMethod(ClassBuilder clb) { clb.withMethod(NAME_METHOD_QUOTED, CodeReflectionSupport.MTD_Quoted, ACC_PUBLIC + ACC_FINAL, new MethodBody(new Consumer() { @Override public void accept(CodeBuilder cob) { diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index 8b923d8e3cc..11d590c62fb 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -48,6 +48,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.*; @@ -472,6 +473,22 @@ public String toText() { } + public static Optional ofQuotable(Quotable q) { + Method method; + try { + method = q.getClass().getMethod("quoted"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + method.setAccessible(true); + Quoted quoted; + try { + quoted = (Quoted) method.invoke(q); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + return Optional.of(quoted); + } /** * Returns the code model of the method body, if present. diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Quotable.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Quotable.java index ef7a79aa3f6..33ac854f2b7 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Quotable.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Quotable.java @@ -27,11 +27,8 @@ /** * Classes implementing this interface support code reflection. That is, they can obtain - * a {@link Quoted} object using {@link #quoted()}, which returns the intermediate + * a {@link Quoted} object using {@link Op#ofQuotable(Quotable)}, which returns the intermediate * representation associated with a lambda expression or method reference. */ public interface Quotable { - default Quoted quoted() { - throw new UnsupportedOperationException(); - } } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index 567cd2fe2a9..ff8242acc30 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -25,7 +25,11 @@ package jdk.incubator.code.interpreter; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import java.lang.invoke.*; +import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; import java.lang.reflect.Proxy; import jdk.incubator.code.*; @@ -487,12 +491,26 @@ static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { .asCollector(Object[].class, lo.parameters().size()); Object fiInstance = MethodHandleProxies.asInterfaceInstance(fi, fProxy); - // If a quotable lambda proxy again to implement Quotable + // If a quotable lambda proxy again to add method Quoted quoted() if (Quotable.class.isAssignableFrom(fi)) { - return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi}, + // Op.ofQuotable(Quotable q) expect q's class to have the method: Quoted quoted() + // that's why we define an interface that contains the method, so that proxy class has it + // and the code of Op.ofQuotable works + byte[] bytes = ClassFile.of().build(ClassDesc.of("I" + System.nanoTime()), classBuilder -> { + classBuilder + .withFlags(AccessFlag.PUBLIC, AccessFlag.INTERFACE, AccessFlag.ABSTRACT) + .withMethod("quoted", MethodTypeDesc.of(Quoted.class.describeConstable().get()), + ClassFile.ACC_PUBLIC | ClassFile.ACC_ABSTRACT, mb -> {}); + }); + Class interfaceQuotedCLass; + try { + interfaceQuotedCLass = l.defineClass(bytes); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi, interfaceQuotedCLass}, (_, method, args) -> { - if (method.getDeclaringClass() == Quotable.class) { - // Implement Quotable::quoted + if (method.getDeclaringClass() == interfaceQuotedCLass) { return new Quoted(lo, capturedValuesAndArguments); } else { // Delegate to FI instance diff --git a/test/jdk/java/lang/reflect/code/TestBuild.java b/test/jdk/java/lang/reflect/code/TestBuild.java index fe4638bf43e..bcc61563f85 100644 --- a/test/jdk/java/lang/reflect/code/TestBuild.java +++ b/test/jdk/java/lang/reflect/code/TestBuild.java @@ -46,7 +46,7 @@ public class TestBuild { public LambdaOp f() { IntBinaryOperator ibo = (IntBinaryOperator & Quotable) (a, b) -> a + b; Quotable iboq = (Quotable) ibo; - return SSA.transform((LambdaOp) iboq.quoted().op()); + return SSA.transform((LambdaOp) Op.ofQuotable(iboq).get().op()); } @Test diff --git a/test/jdk/java/lang/reflect/code/TestLambdaOps.java b/test/jdk/java/lang/reflect/code/TestLambdaOps.java index ef0ec2ecde8..456624c2a09 100644 --- a/test/jdk/java/lang/reflect/code/TestLambdaOps.java +++ b/test/jdk/java/lang/reflect/code/TestLambdaOps.java @@ -153,7 +153,7 @@ static int f(int i) { @Test public void testQuotableModel() { Quotable quotable = (Runnable & Quotable) () -> {}; - Op qop = quotable.quoted().op(); + Op qop = Op.ofQuotable(quotable).get().op(); Op top = qop.ancestorBody().parentOp().ancestorBody().parentOp(); Assert.assertTrue(top instanceof CoreOp.FuncOp); @@ -180,7 +180,7 @@ public void testQuote() { QuotableIntSupplier op = (QuotableIntSupplier) Interpreter.invoke(MethodHandles.lookup(), g, 42); Assert.assertEquals(op.getAsInt(), 42); - Quoted q = op.quoted(); + Quoted q = Op.ofQuotable(op).get(); q.op().writeTo(System.out); Assert.assertEquals(q.capturedValues().size(), 1); Assert.assertEquals(((Var)q.capturedValues().values().iterator().next()).value(), 42); @@ -198,7 +198,7 @@ public void testQuote() { QuotableIntSupplier op = quote(42); Assert.assertEquals(op.getAsInt(), 42); - Quoted q = op.quoted(); + Quoted q = Op.ofQuotable(op).get(); q.op().writeTo(System.out); System.out.print(q.capturedValues().values()); Assert.assertEquals(q.capturedValues().size(), 1); @@ -235,7 +235,7 @@ Iterator methodRefLambdas() { @Test(dataProvider = "methodRefLambdas") public void testIsMethodReference(Quotable q) { - Quoted quoted = q.quoted(); + Quoted quoted = Op.ofQuotable(q).get(); CoreOp.LambdaOp lop = (CoreOp.LambdaOp) quoted.op(); Assert.assertTrue(lop.methodReference().isPresent()); } diff --git a/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java b/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java index ac824911d45..b5d17bbf5ab 100644 --- a/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java +++ b/test/jdk/java/lang/reflect/code/bytecode/TestBytecode.java @@ -386,9 +386,9 @@ static int consume(int i, Func f) { } static int consumeQuotable(int i, QuotableFunc f) { - Assert.assertNotNull(f.quoted()); - Assert.assertNotNull(f.quoted().op()); - Assert.assertTrue(f.quoted().op() instanceof CoreOp.LambdaOp); + Assert.assertNotNull(Op.ofQuotable(f).get()); + Assert.assertNotNull(Op.ofQuotable(f).get().op()); + Assert.assertTrue(Op.ofQuotable(f).get().op() instanceof CoreOp.LambdaOp); return f.apply(i + 1); } diff --git a/test/jdk/java/lang/reflect/code/bytecode/TestNestedCapturingLambda.java b/test/jdk/java/lang/reflect/code/bytecode/TestNestedCapturingLambda.java index acccb36aa37..f424275b12b 100644 --- a/test/jdk/java/lang/reflect/code/bytecode/TestNestedCapturingLambda.java +++ b/test/jdk/java/lang/reflect/code/bytecode/TestNestedCapturingLambda.java @@ -62,7 +62,7 @@ static public int f(int a) { static void test(QIntSupplier s, int a) { @SuppressWarnings("unchecked") - CoreOp.Var capture = (CoreOp.Var) s.quoted().capturedValues().values().iterator().next(); + CoreOp.Var capture = (CoreOp.Var) Op.ofQuotable(s).get().capturedValues().values().iterator().next(); Assert.assertEquals(capture.value().intValue(), a); } diff --git a/test/jdk/java/lang/reflect/code/linq/Queryable.java b/test/jdk/java/lang/reflect/code/linq/Queryable.java index 2ee5a199b0b..5927cf1c9b6 100644 --- a/test/jdk/java/lang/reflect/code/linq/Queryable.java +++ b/test/jdk/java/lang/reflect/code/linq/Queryable.java @@ -46,13 +46,13 @@ public interface Queryable { @SuppressWarnings("unchecked") default Queryable where(QuotablePredicate f) { - LambdaOp l = (LambdaOp) f.quoted().op(); + LambdaOp l = (LambdaOp) Op.ofQuotable(f).get().op(); return (Queryable) insertQuery(elementType(), "where", l); } @SuppressWarnings("unchecked") default Queryable select(QuotableFunction f) { - LambdaOp l = (LambdaOp) f.quoted().op(); + LambdaOp l = (LambdaOp) Op.ofQuotable(f).get().op(); return (Queryable) insertQuery((JavaType) l.invokableType().returnType(), "select", l); } diff --git a/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java b/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java index 5df64764ebf..189a8222bc9 100644 --- a/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java +++ b/test/jdk/java/lang/reflect/code/stream/StreamFuserUsingQuotable.java @@ -71,10 +71,10 @@ static class StreamOp { final LambdaOp lambdaOp; StreamOp(Quotable quotedLambda) { - if (!(quotedLambda.quoted().op() instanceof LambdaOp lambdaOp)) { + if (!(Op.ofQuotable(quotedLambda).get().op() instanceof LambdaOp lambdaOp)) { throw new IllegalArgumentException("Quotable operation is not lambda operation"); } - if (!(quotedLambda.quoted().capturedValues().isEmpty())) { + if (!(Op.ofQuotable(quotedLambda).get().capturedValues().isEmpty())) { throw new IllegalArgumentException("Quotable operation captures values"); } this.lambdaOp = lambdaOp; @@ -197,10 +197,10 @@ void fuseIntermediateOperation(int i, Block.Builder body, Value element, Block.B } public FuncOp forEach(QuotableConsumer quotableConsumer) { - if (!(quotableConsumer.quoted().op() instanceof LambdaOp consumer)) { + if (!(Op.ofQuotable(quotableConsumer).get().op() instanceof LambdaOp consumer)) { throw new IllegalArgumentException("Quotable consumer is not lambda operation"); } - if (!(quotableConsumer.quoted().capturedValues().isEmpty())) { + if (!(Op.ofQuotable(quotableConsumer).get().capturedValues().isEmpty())) { throw new IllegalArgumentException("Quotable consumer captures values"); } @@ -224,16 +224,16 @@ public FuncOp forEach(QuotableConsumer quotableConsumer) { } public FuncOp collect(QuotableSupplier quotableSupplier, QuotableBiConsumer quotableAccumulator) { - if (!(quotableSupplier.quoted().op() instanceof LambdaOp supplier)) { + if (!(Op.ofQuotable(quotableSupplier).get().op() instanceof LambdaOp supplier)) { throw new IllegalArgumentException("Quotable supplier is not lambda operation"); } - if (!(quotableSupplier.quoted().capturedValues().isEmpty())) { + if (!(Op.ofQuotable(quotableSupplier).get().capturedValues().isEmpty())) { throw new IllegalArgumentException("Quotable supplier captures values"); } - if (!(quotableAccumulator.quoted().op() instanceof LambdaOp accumulator)) { + if (!(Op.ofQuotable(quotableAccumulator).get().op() instanceof LambdaOp accumulator)) { throw new IllegalArgumentException("Quotable accumulator is not lambda operation"); } - if (!(quotableAccumulator.quoted().capturedValues().isEmpty())) { + if (!(Op.ofQuotable(quotableAccumulator).get().capturedValues().isEmpty())) { throw new IllegalArgumentException("Quotable accumulator captures values"); } diff --git a/test/langtools/tools/javac/reflect/CodeReflectionTester.java b/test/langtools/tools/javac/reflect/CodeReflectionTester.java index 97ec8d37854..abd73b406cf 100644 --- a/test/langtools/tools/javac/reflect/CodeReflectionTester.java +++ b/test/langtools/tools/javac/reflect/CodeReflectionTester.java @@ -90,7 +90,7 @@ static void check(Field field) throws ReflectiveOperationException { } } else if (Quotable.class.isAssignableFrom(field.getType())) { Quotable quotable = (Quotable) field.get(null); - String found = canonicalizeModel(field, getModelOfQuotedOp(quotable.quoted())); + String found = canonicalizeModel(field, getModelOfQuotedOp(Op.ofQuotable(quotable).get())); String expected = canonicalizeModel(field, ir.value()); if (!found.equals(expected)) { error("Bad IR\nFound:\n%s\n\nExpected:\n%s", found, expected); diff --git a/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java b/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java index 015187f15a6..8668b8fab0f 100644 --- a/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java +++ b/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java @@ -1,3 +1,4 @@ +import jdk.incubator.code.Op; import org.testng.Assert; import org.testng.annotations.Test; @@ -7,7 +8,7 @@ /* * @test - * @summary test that invoking Quotable#quoted returns the same instance + * @summary test that invoking Op#ofQuotable returns the same instance * @modules jdk.incubator.code * @run testng QuotedSameInstanceTest */ @@ -19,7 +20,7 @@ public class QuotedSameInstanceTest { @Test void testWithOneThread() { - Assert.assertSame(q1.quoted(), q1.quoted()); + Assert.assertSame(Op.ofQuotable(q1).get(), Op.ofQuotable(q1).get()); } interface QuotableIntUnaryOperator extends IntUnaryOperator, Quotable { } @@ -27,7 +28,7 @@ interface QuotableIntUnaryOperator extends IntUnaryOperator, Quotable { } @Test void testWithMultiThreads() { - Object[] quotedObjects = IntStream.range(0, 1024).parallel().mapToObj(__ -> q2.quoted()).toArray(); + Object[] quotedObjects = IntStream.range(0, 1024).parallel().mapToObj(__ -> Op.ofQuotable(q2).get()).toArray(); for (int i = 1; i < quotedObjects.length; i++) { Assert.assertSame(quotedObjects[i], quotedObjects[i - 1]); } diff --git a/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java b/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java index b3437f33c16..2a2e35134db 100644 --- a/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java +++ b/test/langtools/tools/javac/reflect/quoted/TestCaptureQuotable.java @@ -51,7 +51,7 @@ public class TestCaptureQuotable { @Test(dataProvider = "ints") public void testCaptureIntParam(int x) { Quotable quotable = (Quotable & IntUnaryOperator)y -> x + y; - Quoted quoted = quotable.quoted(); + Quoted quoted = Op.ofQuotable(quotable).get(); assertEquals(quoted.capturedValues().size(), 1); assertEquals(((Var)quoted.capturedValues().values().iterator().next()).value(), x); List arguments = new ArrayList<>(); @@ -67,7 +67,7 @@ public void testCaptureThisRefAndIntConstant() { final int x = 100; String hello = "hello"; Quotable quotable = (Quotable & ToIntFunction)y -> y.intValue() + hashCode() + hello.length() + x; - Quoted quoted = quotable.quoted(); + Quoted quoted = Op.ofQuotable(quotable).get(); assertEquals(quoted.capturedValues().size(), 3); Iterator it = quoted.capturedValues().values().iterator(); assertEquals(it.next(), this); @@ -94,7 +94,7 @@ public void testCaptureMany() { int i8 = ia[7] = 7; Quotable quotable = (Quotable & IntSupplier) () -> i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8; - Quoted quoted = quotable.quoted(); + Quoted quoted = Op.ofQuotable(quotable).get(); assertEquals(quoted.capturedValues().size(), ia.length); assertEquals(quoted.op().capturedValues(), new ArrayList<>(quoted.capturedValues().keySet())); Iterator it = quoted.capturedValues().values().iterator(); @@ -120,7 +120,7 @@ Quotable quotable() { } Context context = new Context(x); Quotable quotable = context.quotable(); - Quoted quoted = quotable.quoted(); + Quoted quoted = Op.ofQuotable(quotable).get(); assertEquals(quoted.capturedValues().size(), 1); assertEquals(quoted.capturedValues().values().iterator().next(), context); List arguments = new ArrayList<>(); @@ -142,7 +142,7 @@ public Object[][] ints() { public void testCaptureReferenceReceiver(int i) { int prevCount = Box.count; Quotable quotable = (Quotable & IntUnaryOperator)new Box(i)::add; - Quoted quoted = quotable.quoted(); + Quoted quoted = Op.ofQuotable(quotable).get(); assertEquals(Box.count, prevCount + 1); // no duplicate receiver computation! assertEquals(quoted.capturedValues().size(), 1); assertEquals(((Box)((Var)quoted.capturedValues().values().iterator().next()).value()).i, i); From 79ed71a466b931b419f883ccfeedb138d60d9a37 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Wed, 8 Jan 2025 12:33:42 +0100 Subject: [PATCH 03/12] Simplify interpretation LambdaOp --- .../share/classes/jdk/incubator/code/Op.java | 16 +++++++++++++ .../incubator/code/internal/Quotable2.java | 8 +++++++ .../code/interpreter/Interpreter.java | 24 +++---------------- 3 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index 11d590c62fb..23a6e40ad57 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -38,6 +38,7 @@ import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; +import jdk.incubator.code.internal.Quotable2; import jdk.incubator.code.internal.ReflectMethods; import jdk.incubator.code.op.CoreOp.FuncOp; import jdk.incubator.code.type.FunctionType; @@ -474,6 +475,21 @@ public String toText() { public static Optional ofQuotable(Quotable q) { + // Quotable is a marker interface with no method + // the code here, expect class of object q to have the method: Quoted quoted() + // for lambda objects are created through LambdaMetaFactory, we know that their classes has the method + // for lambda objects created via interpreter, the proxy class implements Quotable2 + // so the proxy class has the method + // but it's in a package that's not exported to this module + // so trying to do: method.setAccessible(true), will result in the error: + // module jdk.proxy2 does not "exports com.sun.proxy.jdk.proxy2" to module jdk.incubator.code + // that's why we do it differently by checking if q is instance of Quotable2 + + // also note that lambda classes created via LambdaMetaFactory, can't access Quotable2 because it's not exported + + if (q instanceof Quotable2 q2) { + return Optional.of(q2.quoted()); + } Method method; try { method = q.getClass().getMethod("quoted"); diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java new file mode 100644 index 00000000000..067e896260e --- /dev/null +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java @@ -0,0 +1,8 @@ +package jdk.incubator.code.internal; + +import jdk.incubator.code.Quotable; +import jdk.incubator.code.Quoted; + +public interface Quotable2 extends Quotable { + Quoted quoted(); +} diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index ff8242acc30..56e48589890 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -25,14 +25,11 @@ package jdk.incubator.code.interpreter; -import java.lang.classfile.ClassFile; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; import java.lang.invoke.*; -import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; import java.lang.reflect.Proxy; import jdk.incubator.code.*; +import jdk.incubator.code.internal.Quotable2; import jdk.incubator.code.op.CoreOp; import jdk.incubator.code.type.ArrayType; import jdk.incubator.code.type.FieldRef; @@ -493,24 +490,9 @@ static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { // If a quotable lambda proxy again to add method Quoted quoted() if (Quotable.class.isAssignableFrom(fi)) { - // Op.ofQuotable(Quotable q) expect q's class to have the method: Quoted quoted() - // that's why we define an interface that contains the method, so that proxy class has it - // and the code of Op.ofQuotable works - byte[] bytes = ClassFile.of().build(ClassDesc.of("I" + System.nanoTime()), classBuilder -> { - classBuilder - .withFlags(AccessFlag.PUBLIC, AccessFlag.INTERFACE, AccessFlag.ABSTRACT) - .withMethod("quoted", MethodTypeDesc.of(Quoted.class.describeConstable().get()), - ClassFile.ACC_PUBLIC | ClassFile.ACC_ABSTRACT, mb -> {}); - }); - Class interfaceQuotedCLass; - try { - interfaceQuotedCLass = l.defineClass(bytes); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi, interfaceQuotedCLass}, + return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi, Quotable2.class}, (_, method, args) -> { - if (method.getDeclaringClass() == interfaceQuotedCLass) { + if (method.getDeclaringClass() == Quotable2.class) { return new Quoted(lo, capturedValuesAndArguments); } else { // Delegate to FI instance From f5b541b41640c46f76ca53ab082edb790215fc1a Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Wed, 8 Jan 2025 14:11:03 +0100 Subject: [PATCH 04/12] Add missing javadoc --- .../share/classes/jdk/incubator/code/Op.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index 23a6e40ad57..bf36e2203f5 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -474,6 +474,12 @@ public String toText() { } + /** + * Returns the code model of the Quotable passed in. + * @param q the Quotable we want to get its code model. + * @return the code model of the Quotable passed in. + * @since 99 + */ public static Optional ofQuotable(Quotable q) { // Quotable is a marker interface with no method // the code here, expect class of object q to have the method: Quoted quoted() From 487d2f57e1e4745a26bbe235862a060ec26c4b49 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Wed, 8 Jan 2025 22:35:21 +0100 Subject: [PATCH 05/12] Fix merge --- .../classes/jdk/incubator/code/internal/QuotedHelper.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java index a834f47abf7..9282cd910d7 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotedHelper.java @@ -36,7 +36,4 @@ public class QuotedHelper { public static Quoted makeQuoted(MethodHandles.Lookup lookup, FuncOp op, Object[] args) { return (Quoted)Interpreter.invoke(lookup, op, args); } - public static Quoted makeQuoted(MethodHandles.Lookup lookup, FuncOp op, Object[] args) { - return (Quoted)Interpreter.invoke(lookup, op, args); - } } From a027bb79eae054a11da160ea66f6d477ccf1c43b Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Sat, 11 Jan 2025 00:07:10 +0100 Subject: [PATCH 06/12] Revert to defining an interface for Quotable lambda in the interpreter --- .../sun/tools/javac/comp/LambdaToMethod.java | 2 +- .../share/classes/jdk/incubator/code/Op.java | 16 ------------- .../incubator/code/internal/Quotable2.java | 8 ------- .../code/interpreter/Interpreter.java | 24 ++++++++++++++++--- 4 files changed, 22 insertions(+), 28 deletions(-) delete mode 100644 src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index 9dc30d35f6a..e2f7b576142 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -879,7 +879,7 @@ private JCExpression makeMetafactoryIndyCall(JCFunctionalExpression tree, } } if (isQuotable) { - MethodSymbol opMethodSym = (MethodSymbol)tree.codeModel; + MethodSymbol opMethodSym = tree.codeModel; staticArgs = staticArgs.append(opMethodSym.asHandle()); } if (isSerializable) { diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index bf36e2203f5..c07c155018b 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -38,7 +38,6 @@ import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; -import jdk.incubator.code.internal.Quotable2; import jdk.incubator.code.internal.ReflectMethods; import jdk.incubator.code.op.CoreOp.FuncOp; import jdk.incubator.code.type.FunctionType; @@ -481,21 +480,6 @@ public String toText() { * @since 99 */ public static Optional ofQuotable(Quotable q) { - // Quotable is a marker interface with no method - // the code here, expect class of object q to have the method: Quoted quoted() - // for lambda objects are created through LambdaMetaFactory, we know that their classes has the method - // for lambda objects created via interpreter, the proxy class implements Quotable2 - // so the proxy class has the method - // but it's in a package that's not exported to this module - // so trying to do: method.setAccessible(true), will result in the error: - // module jdk.proxy2 does not "exports com.sun.proxy.jdk.proxy2" to module jdk.incubator.code - // that's why we do it differently by checking if q is instance of Quotable2 - - // also note that lambda classes created via LambdaMetaFactory, can't access Quotable2 because it's not exported - - if (q instanceof Quotable2 q2) { - return Optional.of(q2.quoted()); - } Method method; try { method = q.getClass().getMethod("quoted"); diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java deleted file mode 100644 index 067e896260e..00000000000 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/Quotable2.java +++ /dev/null @@ -1,8 +0,0 @@ -package jdk.incubator.code.internal; - -import jdk.incubator.code.Quotable; -import jdk.incubator.code.Quoted; - -public interface Quotable2 extends Quotable { - Quoted quoted(); -} diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index 56e48589890..ff8242acc30 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -25,11 +25,14 @@ package jdk.incubator.code.interpreter; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import java.lang.invoke.*; +import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; import java.lang.reflect.Proxy; import jdk.incubator.code.*; -import jdk.incubator.code.internal.Quotable2; import jdk.incubator.code.op.CoreOp; import jdk.incubator.code.type.ArrayType; import jdk.incubator.code.type.FieldRef; @@ -490,9 +493,24 @@ static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { // If a quotable lambda proxy again to add method Quoted quoted() if (Quotable.class.isAssignableFrom(fi)) { - return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi, Quotable2.class}, + // Op.ofQuotable(Quotable q) expect q's class to have the method: Quoted quoted() + // that's why we define an interface that contains the method, so that proxy class has it + // and the code of Op.ofQuotable works + byte[] bytes = ClassFile.of().build(ClassDesc.of("I" + System.nanoTime()), classBuilder -> { + classBuilder + .withFlags(AccessFlag.PUBLIC, AccessFlag.INTERFACE, AccessFlag.ABSTRACT) + .withMethod("quoted", MethodTypeDesc.of(Quoted.class.describeConstable().get()), + ClassFile.ACC_PUBLIC | ClassFile.ACC_ABSTRACT, mb -> {}); + }); + Class interfaceQuotedCLass; + try { + interfaceQuotedCLass = l.defineClass(bytes); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi, interfaceQuotedCLass}, (_, method, args) -> { - if (method.getDeclaringClass() == Quotable2.class) { + if (method.getDeclaringClass() == interfaceQuotedCLass) { return new Quoted(lo, capturedValuesAndArguments); } else { // Delegate to FI instance From 99ec363ca6b639fb653c74d016845268b1c2b04b Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Mon, 13 Jan 2025 16:37:01 +0100 Subject: [PATCH 07/12] Handle the case where a Quotable instance's class is a Proxy. --- .../share/classes/jdk/incubator/code/Op.java | 11 +++++-- .../code/interpreter/Interpreter.java | 27 ++--------------- .../QuotableLambdaOpInvocationHandler.java | 30 +++++++++++++++++++ 3 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index c07c155018b..41e782422a0 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -50,6 +50,7 @@ import javax.lang.model.element.Modifier; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.BiFunction; @@ -480,16 +481,22 @@ public String toText() { * @since 99 */ public static Optional ofQuotable(Quotable q) { + Object oq = q; + if (Proxy.isProxyClass(oq.getClass())) { + oq = Proxy.getInvocationHandler(oq); + } + Method method; try { - method = q.getClass().getMethod("quoted"); + method = oq.getClass().getMethod("quoted"); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } method.setAccessible(true); + Quoted quoted; try { - quoted = (Quoted) method.invoke(q); + quoted = (Quoted) method.invoke(oq); } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index ff8242acc30..8a1129dd34e 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -493,30 +493,9 @@ static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { // If a quotable lambda proxy again to add method Quoted quoted() if (Quotable.class.isAssignableFrom(fi)) { - // Op.ofQuotable(Quotable q) expect q's class to have the method: Quoted quoted() - // that's why we define an interface that contains the method, so that proxy class has it - // and the code of Op.ofQuotable works - byte[] bytes = ClassFile.of().build(ClassDesc.of("I" + System.nanoTime()), classBuilder -> { - classBuilder - .withFlags(AccessFlag.PUBLIC, AccessFlag.INTERFACE, AccessFlag.ABSTRACT) - .withMethod("quoted", MethodTypeDesc.of(Quoted.class.describeConstable().get()), - ClassFile.ACC_PUBLIC | ClassFile.ACC_ABSTRACT, mb -> {}); - }); - Class interfaceQuotedCLass; - try { - interfaceQuotedCLass = l.defineClass(bytes); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi, interfaceQuotedCLass}, - (_, method, args) -> { - if (method.getDeclaringClass() == interfaceQuotedCLass) { - return new Quoted(lo, capturedValuesAndArguments); - } else { - // Delegate to FI instance - return method.invoke(fiInstance, args); - } - }); + Quoted quoted = new Quoted(lo, capturedValuesAndArguments); + return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi}, + new QuotableLambdaOpInvocationHandler(fiInstance, quoted)); } else { return fiInstance; } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java new file mode 100644 index 00000000000..bd974e3cf14 --- /dev/null +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java @@ -0,0 +1,30 @@ +package jdk.incubator.code.interpreter; + +import jdk.incubator.code.Quoted; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Objects; + +public class QuotableLambdaOpInvocationHandler implements InvocationHandler { + private final Object fiInstance; + private final Quoted quoted; + + public QuotableLambdaOpInvocationHandler(Object fiInstance, Quoted quoted) { + this.fiInstance = fiInstance; + this.quoted = quoted; + } + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (Objects.equals(method.getName(), "quoted") && method.getParameterCount() == 0) { + return quoted(); + } else { + // Delegate to FI instance + return method.invoke(fiInstance, args); + } + } + + public final Quoted quoted() { + return quoted; + } +} From ec8b4fcca9b2ef9c375822df881fe10932d113c1 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Mon, 13 Jan 2025 18:03:24 +0100 Subject: [PATCH 08/12] Move QuotableLambdaOpInvocationHandler to internal package --- .../QuotableLambdaOpInvocationHandler.java | 2 +- .../classes/jdk/incubator/code/interpreter/Interpreter.java | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) rename src/jdk.incubator.code/share/classes/jdk/incubator/code/{interpreter => internal}/QuotableLambdaOpInvocationHandler.java (95%) diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java similarity index 95% rename from src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java rename to src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java index bd974e3cf14..a4f7530dc6b 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/QuotableLambdaOpInvocationHandler.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java @@ -1,4 +1,4 @@ -package jdk.incubator.code.interpreter; +package jdk.incubator.code.internal; import jdk.incubator.code.Quoted; diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index 8a1129dd34e..fce0805c837 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -25,14 +25,11 @@ package jdk.incubator.code.interpreter; -import java.lang.classfile.ClassFile; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; import java.lang.invoke.*; -import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; import java.lang.reflect.Proxy; import jdk.incubator.code.*; +import jdk.incubator.code.internal.QuotableLambdaOpInvocationHandler; import jdk.incubator.code.op.CoreOp; import jdk.incubator.code.type.ArrayType; import jdk.incubator.code.type.FieldRef; From 9da5b6512bad1232494f46cf428a5da65f770f94 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Mon, 13 Jan 2025 18:11:51 +0100 Subject: [PATCH 09/12] Make Quotable LambdaOp Invocation Handler an inner anonymous class --- .../QuotableLambdaOpInvocationHandler.java | 30 ------------------- .../code/interpreter/Interpreter.java | 21 +++++++++++-- 2 files changed, 18 insertions(+), 33 deletions(-) delete mode 100644 src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java deleted file mode 100644 index a4f7530dc6b..00000000000 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/internal/QuotableLambdaOpInvocationHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package jdk.incubator.code.internal; - -import jdk.incubator.code.Quoted; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.Objects; - -public class QuotableLambdaOpInvocationHandler implements InvocationHandler { - private final Object fiInstance; - private final Quoted quoted; - - public QuotableLambdaOpInvocationHandler(Object fiInstance, Quoted quoted) { - this.fiInstance = fiInstance; - this.quoted = quoted; - } - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (Objects.equals(method.getName(), "quoted") && method.getParameterCount() == 0) { - return quoted(); - } else { - // Delegate to FI instance - return method.invoke(fiInstance, args); - } - } - - public final Quoted quoted() { - return quoted; - } -} diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index fce0805c837..0b8f05e889b 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -27,9 +27,10 @@ import java.lang.invoke.*; import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; import java.lang.reflect.Proxy; import jdk.incubator.code.*; -import jdk.incubator.code.internal.QuotableLambdaOpInvocationHandler; import jdk.incubator.code.op.CoreOp; import jdk.incubator.code.type.ArrayType; import jdk.incubator.code.type.FieldRef; @@ -490,9 +491,23 @@ static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { // If a quotable lambda proxy again to add method Quoted quoted() if (Quotable.class.isAssignableFrom(fi)) { - Quoted quoted = new Quoted(lo, capturedValuesAndArguments); return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class[]{fi}, - new QuotableLambdaOpInvocationHandler(fiInstance, quoted)); + new InvocationHandler() { + private final Quoted quoted = new Quoted(lo, capturedValuesAndArguments); + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (Objects.equals(method.getName(), "quoted") && method.getParameterCount() == 0) { + return quoted(); + } else { + // Delegate to FI instance + return method.invoke(fiInstance, args); + } + } + + public Quoted quoted() { + return quoted; + } + }); } else { return fiInstance; } From 8d3ca82c6900efeca5ab05acdbdfa9dab55cf7a7 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Fri, 17 Jan 2025 02:12:32 +0100 Subject: [PATCH 10/12] Change the name of the magic method we call to get what's quoted --- .../classes/java/lang/invoke/InnerClassLambdaMetafactory.java | 2 +- .../share/classes/jdk/incubator/code/Op.java | 2 +- .../classes/jdk/incubator/code/interpreter/Interpreter.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index 4ff7b05b022..97606479887 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -88,7 +88,7 @@ public void accept(MethodBuilder mb) { private static final boolean disableEagerInitialization; - private static final String NAME_METHOD_QUOTED = "quoted"; + private static final String NAME_METHOD_QUOTED = "__internal_quoted"; private static final String quotedInstanceFieldName = "quoted"; static { diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index 41e782422a0..7ef5e6179fb 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -488,7 +488,7 @@ public static Optional ofQuotable(Quotable q) { Method method; try { - method = oq.getClass().getMethod("quoted"); + method = oq.getClass().getMethod("__internal_quoted"); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java index 0b8f05e889b..4d04aab7c82 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/interpreter/Interpreter.java @@ -497,14 +497,14 @@ static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Objects.equals(method.getName(), "quoted") && method.getParameterCount() == 0) { - return quoted(); + return __internal_quoted(); } else { // Delegate to FI instance return method.invoke(fiInstance, args); } } - public Quoted quoted() { + public Quoted __internal_quoted() { return quoted; } }); From 19aeb96ff1da78a6b76cab22a3c286f1530db3a3 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Fri, 17 Jan 2025 02:19:13 +0100 Subject: [PATCH 11/12] Return an empty optional if Quotable is a proxy and add a note about this --- .../share/classes/jdk/incubator/code/Op.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java index 7ef5e6179fb..432567ce17d 100644 --- a/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java +++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/Op.java @@ -478,6 +478,8 @@ public String toText() { * Returns the code model of the Quotable passed in. * @param q the Quotable we want to get its code model. * @return the code model of the Quotable passed in. + * @apiNote If the Quotable instance is a proxy instance, then the quoted code model is inaccessible and this method + * returns an empty optional. * @since 99 */ public static Optional ofQuotable(Quotable q) { @@ -490,7 +492,7 @@ public static Optional ofQuotable(Quotable q) { try { method = oq.getClass().getMethod("__internal_quoted"); } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + return Optional.empty(); } method.setAccessible(true); From 1613b2abe38097cb038c65decd88f5b1c535e275 Mon Sep 17 00:00:00 2001 From: Mourad Abbay Date: Fri, 17 Jan 2025 02:21:07 +0100 Subject: [PATCH 12/12] Test that Op#ofQuotable returns same object for Quotable created via interpreter --- .../javac/reflect/QuotedSameInstanceTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java b/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java index 8668b8fab0f..fb51b23cc95 100644 --- a/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java +++ b/test/langtools/tools/javac/reflect/QuotedSameInstanceTest.java @@ -1,8 +1,13 @@ +import jdk.incubator.code.CodeReflection; import jdk.incubator.code.Op; +import jdk.incubator.code.interpreter.Interpreter; import org.testng.Assert; import org.testng.annotations.Test; import jdk.incubator.code.Quotable; + +import java.lang.invoke.MethodHandles; +import java.util.function.IntSupplier; import java.util.function.IntUnaryOperator; import java.util.stream.IntStream; @@ -33,4 +38,22 @@ void testWithMultiThreads() { Assert.assertSame(quotedObjects[i], quotedObjects[i - 1]); } } + + public interface QuotableIntSupplier extends IntSupplier, Quotable {} + @CodeReflection + static Quotable q() { + QuotableIntSupplier r = () -> 8; + return r; + } + + @Test + void testMultiThreadsViaInterpreter() throws NoSuchMethodException { + var qm = this.getClass().getDeclaredMethod("q"); + var q = Op.ofMethod(qm).get(); + Quotable quotable = (Quotable) Interpreter.invoke(MethodHandles.lookup(), q); + Object[] quotedObjects = IntStream.range(0, 1024).parallel().mapToObj(__ -> Op.ofQuotable(quotable).get()).toArray(); + for (int i = 1; i < quotedObjects.length; i++) { + Assert.assertSame(quotedObjects[i], quotedObjects[i - 1]); + } + } }