Skip to content

Commit 30cc33e

Browse files
authored
Fix Painless Lambdas for Java 9 (#24070)
Replaces LambdaMetaFactory with LambdaBootstrap, a custom solution for lambdas in Painless using a design similar to LambdaMetaFactory, but allows for custom adaptation of types which recent changes to LambdaMetaFactory no longer allowed.
1 parent 026bf2e commit 30cc33e

File tree

16 files changed

+890
-461
lines changed

16 files changed

+890
-461
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,19 @@ static final class Loader extends SecureClassLoader {
7979
* @param bytes The generated byte code.
8080
* @return A Class object extending {@link PainlessScript}.
8181
*/
82-
Class<? extends PainlessScript> define(String name, byte[] bytes) {
82+
Class<? extends PainlessScript> defineScript(String name, byte[] bytes) {
8383
return defineClass(name, bytes, 0, bytes.length, CODESOURCE).asSubclass(PainlessScript.class);
8484
}
85+
86+
/**
87+
* Generates a Class object for a lambda method.
88+
* @param name The name of the class.
89+
* @param bytes The generated byte code.
90+
* @return A Class object.
91+
*/
92+
Class<?> defineLambda(String name, byte[] bytes) {
93+
return defineClass(name, bytes, 0, bytes.length);
94+
}
8595
}
8696

8797
/**
@@ -110,7 +120,7 @@ static <T> T compile(Loader loader, Class<T> iface, String name, String source,
110120
root.write();
111121

112122
try {
113-
Class<? extends PainlessScript> clazz = loader.define(CLASS_NAME, root.getBytes());
123+
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
114124
clazz.getField("$DEFINITION").set(null, definition);
115125
java.lang.reflect.Constructor<? extends PainlessScript> constructor =
116126
clazz.getConstructor(String.class, String.class, BitSet.class);

modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.elasticsearch.painless.Definition.RuntimeClass;
2424

2525
import java.lang.invoke.CallSite;
26-
import java.lang.invoke.LambdaMetafactory;
2726
import java.lang.invoke.MethodHandle;
2827
import java.lang.invoke.MethodHandles;
2928
import java.lang.invoke.MethodHandles.Lookup;
@@ -132,7 +131,7 @@ private ArrayLengthHelper() {}
132131
} catch (final ReflectiveOperationException roe) {
133132
throw new AssertionError(roe);
134133
}
135-
134+
136135
// lookup up the factory for arraylength MethodHandle (intrinsic) from Java 9:
137136
// https://bugs.openjdk.java.net/browse/JDK-8156915
138137
MethodHandle arrayLengthMHFactory;
@@ -150,7 +149,7 @@ private ArrayLengthHelper() {}
150149
static <T extends Throwable> void rethrow(Throwable t) throws T {
151150
throw (T) t;
152151
}
153-
152+
154153
/** Returns an array length getter MethodHandle for the given array type */
155154
static MethodHandle arrayLengthGetter(Class<?> arrayType) {
156155
if (JAVA9_ARRAY_LENGTH_MH_FACTORY != null) {
@@ -206,7 +205,7 @@ static Method lookupMethodInternal(Definition definition, Class<?> receiverClass
206205
}
207206
}
208207
}
209-
208+
210209
throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] with [" + arity + "] arguments " +
211210
"for class [" + receiverClass.getCanonicalName() + "].");
212211
}
@@ -239,15 +238,15 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
239238
if (recipeString.isEmpty()) {
240239
return lookupMethodInternal(definition, receiverClass, name, numArguments - 1).handle;
241240
}
242-
241+
243242
// convert recipe string to a bitset for convenience (the code below should be refactored...)
244243
BitSet lambdaArgs = new BitSet();
245244
for (int i = 0; i < recipeString.length(); i++) {
246245
lambdaArgs.set(recipeString.charAt(i));
247246
}
248247

249248
// otherwise: first we have to compute the "real" arity. This is because we have extra arguments:
250-
// e.g. f(a, g(x), b, h(y), i()) looks like f(a, g, x, b, h, y, i).
249+
// e.g. f(a, g(x), b, h(y), i()) looks like f(a, g, x, b, h, y, i).
251250
int arity = callSiteType.parameterCount() - 1;
252251
int upTo = 1;
253252
for (int i = 1; i < numArguments; i++) {
@@ -257,7 +256,7 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
257256
arity -= numCaptures;
258257
}
259258
}
260-
259+
261260
// lookup the method with the proper arity, then we know everything (e.g. interface types of parameters).
262261
// based on these we can finally link any remaining lambdas that were deferred.
263262
Method method = lookupMethodInternal(definition, receiverClass, name, arity);
@@ -268,7 +267,7 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
268267
for (int i = 1; i < numArguments; i++) {
269268
// its a functional reference, replace the argument with an impl
270269
if (lambdaArgs.get(i - 1)) {
271-
// decode signature of form 'type.call,2'
270+
// decode signature of form 'type.call,2'
272271
String signature = (String) args[upTo++];
273272
int separator = signature.lastIndexOf('.');
274273
int separator2 = signature.indexOf(',');
@@ -313,10 +312,10 @@ static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodTyp
313312
replaced += numCaptures;
314313
}
315314
}
316-
315+
317316
return handle;
318317
}
319-
318+
320319
/**
321320
* Returns an implementation of interfaceClass that calls receiverClass.name
322321
* <p>
@@ -335,7 +334,7 @@ static MethodHandle lookupReference(Definition definition, Lookup lookup, String
335334
return lookupReferenceInternal(definition, lookup, interfaceType, implMethod.owner.name,
336335
implMethod.name, receiverClass);
337336
}
338-
337+
339338
/** Returns a method handle to an implementation of clazz, given method reference signature. */
340339
private static MethodHandle lookupReferenceInternal(Definition definition, Lookup lookup,
341340
Definition.Type clazz, String type, String call, Class<?>... captures)
@@ -351,47 +350,37 @@ private static MethodHandle lookupReferenceInternal(Definition definition, Looku
351350
int arity = interfaceMethod.arguments.size() + captures.length;
352351
final MethodHandle handle;
353352
try {
354-
MethodHandle accessor = lookup.findStaticGetter(lookup.lookupClass(),
355-
getUserFunctionHandleFieldName(call, arity),
353+
MethodHandle accessor = lookup.findStaticGetter(lookup.lookupClass(),
354+
getUserFunctionHandleFieldName(call, arity),
356355
MethodHandle.class);
357-
handle = (MethodHandle) accessor.invokeExact();
356+
handle = (MethodHandle)accessor.invokeExact();
358357
} catch (NoSuchFieldException | IllegalAccessException e) {
359358
// is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail
360359
// because the arity does not match the expected interface type.
361360
if (call.contains("$")) {
362-
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
361+
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
363362
"] in [" + clazz.clazz + "]");
364363
}
365364
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
366365
}
367-
ref = new FunctionRef(clazz, interfaceMethod, handle, captures.length);
366+
ref = new FunctionRef(clazz, interfaceMethod, call, handle.type(), captures.length);
368367
} else {
369368
// whitelist lookup
370369
ref = new FunctionRef(definition, clazz, type, call, captures.length);
371370
}
372-
final CallSite callSite;
373-
if (ref.needsBridges()) {
374-
callSite = LambdaMetafactory.altMetafactory(lookup,
375-
ref.invokedName,
376-
ref.invokedType,
377-
ref.samMethodType,
378-
ref.implMethod,
379-
ref.samMethodType,
380-
LambdaMetafactory.FLAG_BRIDGES,
381-
1,
382-
ref.interfaceMethodType);
383-
} else {
384-
callSite = LambdaMetafactory.altMetafactory(lookup,
385-
ref.invokedName,
386-
ref.invokedType,
387-
ref.samMethodType,
388-
ref.implMethod,
389-
ref.samMethodType,
390-
0);
391-
}
371+
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
372+
lookup,
373+
ref.interfaceMethodName,
374+
ref.factoryMethodType,
375+
ref.interfaceMethodType,
376+
ref.delegateClassName,
377+
ref.delegateInvokeType,
378+
ref.delegateMethodName,
379+
ref.delegateMethodType
380+
);
392381
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz.clazz, captures));
393382
}
394-
383+
395384
/** gets the field name used to lookup up the MethodHandle for a function. */
396385
public static String getUserFunctionHandleFieldName(String name, int arity) {
397386
return "handle$" + name + "$" + arity;
@@ -595,7 +584,7 @@ static MethodHandle lookupArrayLoad(Class<?> receiverClass) {
595584
throw new IllegalArgumentException("Attempting to address a non-array type " +
596585
"[" + receiverClass.getCanonicalName() + "] as an array.");
597586
}
598-
587+
599588
/** Helper class for isolating MethodHandles and methods to get iterators over arrays
600589
* (to emulate "enhanced for loop" using MethodHandles). These cause boxing, and are not as efficient
601590
* as they could be, but works.

modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.elasticsearch.painless;
22

3+
import java.util.List;
34
import java.util.function.Function;
45

56
/*
@@ -25,11 +26,11 @@
2526
public class FeatureTest {
2627
private int x;
2728
private int y;
28-
29+
2930
/** empty ctor */
3031
public FeatureTest() {
3132
}
32-
33+
3334
/** ctor with params */
3435
public FeatureTest(int x, int y) {
3536
this.x = x;
@@ -60,14 +61,18 @@ public void setY(int y) {
6061
public static boolean overloadedStatic() {
6162
return true;
6263
}
63-
64+
6465
/** static method that returns what you ask it */
6566
public static boolean overloadedStatic(boolean whatToReturn) {
6667
return whatToReturn;
6768
}
68-
69+
6970
/** method taking two functions! */
7071
public Object twoFunctionsOfX(Function<Object,Object> f, Function<Object,Object> g) {
7172
return f.apply(g.apply(x));
7273
}
74+
75+
public void listInput(List<Object> list) {
76+
77+
}
7378
}

0 commit comments

Comments
 (0)