Skip to content

Commit 7234730

Browse files
authored
Replace bridge methods with filtered methods in Painless (elastic#88100)
The invokedynamic instruction does not perfectly follow the Painless casting model opting to add bridge methods where necessary to ensure symmetric behavior between compile-time and run-time casting using boxed types. This change replaces the specialized class loader and bridge methods using filtered method handles instead. This reduces the overall complexity of runtime casting.
1 parent 56befd0 commit 7234730

File tree

2 files changed

+76
-149
lines changed

2 files changed

+76
-149
lines changed

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.invoke.MethodType;
2020
import java.util.BitSet;
2121
import java.util.Collections;
22+
import java.util.HashMap;
2223
import java.util.Iterator;
2324
import java.util.List;
2425
import java.util.Map;
@@ -146,6 +147,8 @@ private ArrayLengthHelper() {}
146147
/** factory for arraylength MethodHandle (intrinsic) from Java 9 (pkg-private for tests) */
147148
static final MethodHandle JAVA9_ARRAY_LENGTH_MH_FACTORY;
148149

150+
public static final Map<Class<?>, MethodHandle> DEF_TO_BOXED_TYPE_IMPLICIT_CAST;
151+
149152
static {
150153
final MethodHandles.Lookup methodHandlesLookup = MethodHandles.publicLookup();
151154

@@ -182,6 +185,43 @@ private ArrayLengthHelper() {}
182185
arrayLengthMHFactory = null;
183186
}
184187
JAVA9_ARRAY_LENGTH_MH_FACTORY = arrayLengthMHFactory;
188+
189+
Map<Class<?>, MethodHandle> defToBoxedTypeImplicitCast = new HashMap<>();
190+
191+
try {
192+
defToBoxedTypeImplicitCast.put(
193+
Byte.class,
194+
methodHandlesLookup.findStatic(Def.class, "defToByteImplicit", MethodType.methodType(Byte.class, Object.class))
195+
);
196+
defToBoxedTypeImplicitCast.put(
197+
Short.class,
198+
methodHandlesLookup.findStatic(Def.class, "defToShortImplicit", MethodType.methodType(Short.class, Object.class))
199+
);
200+
defToBoxedTypeImplicitCast.put(
201+
Character.class,
202+
methodHandlesLookup.findStatic(Def.class, "defToCharacterImplicit", MethodType.methodType(Character.class, Object.class))
203+
);
204+
defToBoxedTypeImplicitCast.put(
205+
Integer.class,
206+
methodHandlesLookup.findStatic(Def.class, "defToIntegerImplicit", MethodType.methodType(Integer.class, Object.class))
207+
);
208+
defToBoxedTypeImplicitCast.put(
209+
Long.class,
210+
methodHandlesLookup.findStatic(Def.class, "defToLongImplicit", MethodType.methodType(Long.class, Object.class))
211+
);
212+
defToBoxedTypeImplicitCast.put(
213+
Float.class,
214+
methodHandlesLookup.findStatic(Def.class, "defToFloatImplicit", MethodType.methodType(Float.class, Object.class))
215+
);
216+
defToBoxedTypeImplicitCast.put(
217+
Double.class,
218+
methodHandlesLookup.findStatic(Def.class, "defToDoubleImplicit", MethodType.methodType(Double.class, Object.class))
219+
);
220+
} catch (NoSuchMethodException | IllegalAccessException exception) {
221+
throw new IllegalStateException(exception);
222+
}
223+
224+
DEF_TO_BOXED_TYPE_IMPLICIT_CAST = Collections.unmodifiableMap(defToBoxedTypeImplicitCast);
185225
}
186226

187227
/** Hack to rethrow unknown Exceptions from {@link MethodHandle#invokeExact}: */

modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java

Lines changed: 36 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@
88

99
package org.elasticsearch.painless.lookup;
1010

11-
import org.elasticsearch.bootstrap.BootstrapInfo;
1211
import org.elasticsearch.common.util.Maps;
1312
import org.elasticsearch.painless.Def;
14-
import org.elasticsearch.painless.MethodWriter;
15-
import org.elasticsearch.painless.WriterConstants;
1613
import org.elasticsearch.painless.spi.Whitelist;
1714
import org.elasticsearch.painless.spi.WhitelistClass;
1815
import org.elasticsearch.painless.spi.WhitelistClassBinding;
@@ -25,9 +22,6 @@
2522
import org.elasticsearch.painless.spi.annotation.CompileTimeOnlyAnnotation;
2623
import org.elasticsearch.painless.spi.annotation.InjectConstantAnnotation;
2724
import org.elasticsearch.painless.spi.annotation.NoImportAnnotation;
28-
import org.objectweb.asm.ClassWriter;
29-
import org.objectweb.asm.Opcodes;
30-
import org.objectweb.asm.commons.GeneratorAdapter;
3125

3226
import java.lang.invoke.MethodHandle;
3327
import java.lang.invoke.MethodHandles;
@@ -37,13 +31,6 @@
3731
import java.lang.reflect.Field;
3832
import java.lang.reflect.Method;
3933
import java.lang.reflect.Modifier;
40-
import java.net.MalformedURLException;
41-
import java.net.URL;
42-
import java.security.AccessController;
43-
import java.security.CodeSource;
44-
import java.security.PrivilegedAction;
45-
import java.security.SecureClassLoader;
46-
import java.security.cert.Certificate;
4734
import java.util.ArrayList;
4835
import java.util.Arrays;
4936
import java.util.Collections;
@@ -56,15 +43,6 @@
5643
import java.util.function.Supplier;
5744
import java.util.regex.Pattern;
5845

59-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_BYTE_IMPLICIT;
60-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_CHARACTER_IMPLICIT;
61-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_DOUBLE_IMPLICIT;
62-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_FLOAT_IMPLICIT;
63-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_INTEGER_IMPLICIT;
64-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_LONG_IMPLICIT;
65-
import static org.elasticsearch.painless.WriterConstants.DEF_TO_B_SHORT_IMPLICIT;
66-
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
67-
import static org.elasticsearch.painless.WriterConstants.OBJECT_TYPE;
6846
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_CLASS_NAME;
6947
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessConstructorKey;
7048
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey;
@@ -75,42 +53,17 @@
7553

7654
public final class PainlessLookupBuilder {
7755

78-
private static final class BridgeLoader extends SecureClassLoader {
79-
BridgeLoader(ClassLoader parent) {
80-
super(parent);
81-
}
82-
83-
@Override
84-
public Class<?> findClass(String name) throws ClassNotFoundException {
85-
return Def.class.getName().equals(name) ? Def.class : super.findClass(name);
86-
}
87-
88-
Class<?> defineBridge(String name, byte[] bytes) {
89-
return defineClass(name, bytes, 0, bytes.length, CODESOURCE);
90-
}
91-
}
92-
93-
private static final CodeSource CODESOURCE;
94-
9556
private static final Map<PainlessConstructor, PainlessConstructor> painlessConstructorCache = new HashMap<>();
9657
private static final Map<PainlessMethod, PainlessMethod> painlessMethodCache = new HashMap<>();
9758
private static final Map<PainlessField, PainlessField> painlessFieldCache = new HashMap<>();
9859
private static final Map<PainlessClassBinding, PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
9960
private static final Map<PainlessInstanceBinding, PainlessInstanceBinding> painlessInstanceBindingCache = new HashMap<>();
100-
private static final Map<PainlessMethod, PainlessMethod> painlessBridgeCache = new HashMap<>();
61+
private static final Map<PainlessMethod, PainlessMethod> painlessFilteredCache = new HashMap<>();
10162

10263
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
10364
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
10465
private static final Pattern FIELD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
10566

106-
static {
107-
try {
108-
CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[]) null);
109-
} catch (MalformedURLException mue) {
110-
throw new RuntimeException(mue);
111-
}
112-
}
113-
11467
public static PainlessLookup buildFromWhitelists(List<Whitelist> whitelists) {
11568
PainlessLookupBuilder painlessLookupBuilder = new PainlessLookupBuilder();
11669
String origin = "internal error";
@@ -2216,7 +2169,9 @@ private void setFunctionalInterfaceMethod(Class<?> targetClass, PainlessClassBui
22162169
* run-time resulting from calls with a def type value target.
22172170
*/
22182171
private void generateRuntimeMethods() {
2219-
for (PainlessClassBuilder painlessClassBuilder : classesToPainlessClassBuilders.values()) {
2172+
for (Map.Entry<Class<?>, PainlessClassBuilder> painlessClassBuilderEntry : classesToPainlessClassBuilders.entrySet()) {
2173+
Class<?> targetClass = painlessClassBuilderEntry.getKey();
2174+
PainlessClassBuilder painlessClassBuilder = painlessClassBuilderEntry.getValue();
22202175
painlessClassBuilder.runtimeMethods.putAll(painlessClassBuilder.methods);
22212176

22222177
for (PainlessMethod painlessMethod : painlessClassBuilder.runtimeMethods.values()) {
@@ -2228,63 +2183,25 @@ private void generateRuntimeMethods() {
22282183
|| typeParameter == Long.class
22292184
|| typeParameter == Float.class
22302185
|| typeParameter == Double.class) {
2231-
generateBridgeMethod(painlessClassBuilder, painlessMethod);
2186+
generateFilteredMethod(targetClass, painlessClassBuilder, painlessMethod);
22322187
}
22332188
}
22342189
}
22352190
}
22362191
}
22372192

2238-
private void generateBridgeMethod(PainlessClassBuilder painlessClassBuilder, PainlessMethod painlessMethod) {
2193+
private void generateFilteredMethod(Class<?> targetClass, PainlessClassBuilder painlessClassBuilder, PainlessMethod painlessMethod) {
22392194
String painlessMethodKey = buildPainlessMethodKey(painlessMethod.javaMethod().getName(), painlessMethod.typeParameters().size());
2240-
PainlessMethod bridgePainlessMethod = painlessBridgeCache.get(painlessMethod);
2195+
PainlessMethod filteredPainlessMethod = painlessFilteredCache.get(painlessMethod);
22412196

2242-
if (bridgePainlessMethod == null) {
2197+
if (filteredPainlessMethod == null) {
22432198
Method javaMethod = painlessMethod.javaMethod();
22442199
boolean isStatic = Modifier.isStatic(painlessMethod.javaMethod().getModifiers());
2245-
2246-
int bridgeClassFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
2247-
int bridgeClassAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
2248-
String bridgeClassName = "org/elasticsearch/painless/Bridge$"
2249-
+ javaMethod.getDeclaringClass().getSimpleName()
2250-
+ "$"
2251-
+ javaMethod.getName();
2252-
ClassWriter bridgeClassWriter = new ClassWriter(bridgeClassFrames);
2253-
bridgeClassWriter.visit(
2254-
WriterConstants.CLASS_VERSION,
2255-
bridgeClassAccess,
2256-
bridgeClassName,
2257-
null,
2258-
OBJECT_TYPE.getInternalName(),
2259-
null
2260-
);
2261-
2262-
org.objectweb.asm.commons.Method bridgeConstructorType = new org.objectweb.asm.commons.Method(
2263-
"<init>",
2264-
MethodType.methodType(void.class).toMethodDescriptorString()
2265-
);
2266-
GeneratorAdapter bridgeConstructorWriter = new GeneratorAdapter(
2267-
Opcodes.ASM5,
2268-
bridgeConstructorType,
2269-
bridgeClassWriter.visitMethod(
2270-
Opcodes.ACC_PRIVATE,
2271-
bridgeConstructorType.getName(),
2272-
bridgeConstructorType.getDescriptor(),
2273-
null,
2274-
null
2275-
)
2276-
);
2277-
bridgeConstructorWriter.visitCode();
2278-
bridgeConstructorWriter.loadThis();
2279-
bridgeConstructorWriter.invokeConstructor(OBJECT_TYPE, bridgeConstructorType);
2280-
bridgeConstructorWriter.returnValue();
2281-
bridgeConstructorWriter.endMethod();
2282-
2283-
int bridgeTypeParameterOffset = isStatic ? 0 : 1;
2284-
List<Class<?>> bridgeTypeParameters = new ArrayList<>(javaMethod.getParameterTypes().length + bridgeTypeParameterOffset);
2200+
int filteredTypeParameterOffset = isStatic ? 0 : 1;
2201+
List<Class<?>> filteredTypeParameters = new ArrayList<>(javaMethod.getParameterTypes().length + filteredTypeParameterOffset);
22852202

22862203
if (isStatic == false) {
2287-
bridgeTypeParameters.add(javaMethod.getDeclaringClass());
2204+
filteredTypeParameters.add(javaMethod.getDeclaringClass());
22882205
}
22892206

22902207
for (Class<?> typeParameter : javaMethod.getParameterTypes()) {
@@ -2295,78 +2212,48 @@ private void generateBridgeMethod(PainlessClassBuilder painlessClassBuilder, Pai
22952212
|| typeParameter == Long.class
22962213
|| typeParameter == Float.class
22972214
|| typeParameter == Double.class) {
2298-
bridgeTypeParameters.add(Object.class);
2215+
filteredTypeParameters.add(Object.class);
22992216
} else {
2300-
bridgeTypeParameters.add(typeParameter);
2217+
filteredTypeParameters.add(typeParameter);
23012218
}
23022219
}
23032220

2304-
MethodType bridgeMethodType = MethodType.methodType(painlessMethod.returnType(), bridgeTypeParameters);
2305-
MethodWriter bridgeMethodWriter = new MethodWriter(
2306-
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
2307-
new org.objectweb.asm.commons.Method(painlessMethod.javaMethod().getName(), bridgeMethodType.toMethodDescriptorString()),
2308-
bridgeClassWriter,
2309-
null,
2310-
null
2311-
);
2312-
bridgeMethodWriter.visitCode();
2313-
2314-
if (isStatic == false) {
2315-
bridgeMethodWriter.loadArg(0);
2316-
}
2317-
2318-
for (int typeParameterCount = 0; typeParameterCount < javaMethod.getParameterTypes().length; ++typeParameterCount) {
2319-
bridgeMethodWriter.loadArg(typeParameterCount + bridgeTypeParameterOffset);
2320-
Class<?> typeParameter = javaMethod.getParameterTypes()[typeParameterCount];
2321-
2322-
if (typeParameter == Byte.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_BYTE_IMPLICIT);
2323-
else if (typeParameter == Short.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_SHORT_IMPLICIT);
2324-
else if (typeParameter == Character.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_CHARACTER_IMPLICIT);
2325-
else if (typeParameter == Integer.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_INTEGER_IMPLICIT);
2326-
else if (typeParameter == Long.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_LONG_IMPLICIT);
2327-
else if (typeParameter == Float.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_FLOAT_IMPLICIT);
2328-
else if (typeParameter == Double.class) bridgeMethodWriter.invokeStatic(DEF_UTIL_TYPE, DEF_TO_B_DOUBLE_IMPLICIT);
2329-
}
2330-
2331-
bridgeMethodWriter.invokeMethodCall(painlessMethod);
2332-
bridgeMethodWriter.returnValue();
2333-
bridgeMethodWriter.endMethod();
2334-
2335-
bridgeClassWriter.visitEnd();
2221+
MethodType filteredMethodType = MethodType.methodType(painlessMethod.returnType(), filteredTypeParameters);
2222+
MethodHandle filteredMethodHandle = painlessMethod.methodHandle();
23362223

23372224
try {
2338-
BridgeLoader bridgeLoader = AccessController.doPrivileged(new PrivilegedAction<BridgeLoader>() {
2339-
@Override
2340-
public BridgeLoader run() {
2341-
return new BridgeLoader(javaMethod.getDeclaringClass().getClassLoader());
2225+
for (int typeParameterCount = 0; typeParameterCount < javaMethod.getParameterTypes().length; ++typeParameterCount) {
2226+
Class<?> typeParameter = javaMethod.getParameterTypes()[typeParameterCount];
2227+
MethodHandle castMethodHandle = Def.DEF_TO_BOXED_TYPE_IMPLICIT_CAST.get(typeParameter);
2228+
2229+
if (castMethodHandle != null) {
2230+
filteredMethodHandle = MethodHandles.filterArguments(
2231+
filteredMethodHandle,
2232+
typeParameterCount + filteredTypeParameterOffset,
2233+
castMethodHandle
2234+
);
23422235
}
2343-
});
2236+
}
23442237

2345-
Class<?> bridgeClass = bridgeLoader.defineBridge(bridgeClassName.replace('/', '.'), bridgeClassWriter.toByteArray());
2346-
Method bridgeMethod = bridgeClass.getMethod(
2347-
painlessMethod.javaMethod().getName(),
2348-
bridgeTypeParameters.toArray(new Class<?>[0])
2349-
);
2350-
MethodHandle bridgeHandle = lookup(bridgeClass).unreflect(bridgeClass.getMethods()[0]);
2351-
bridgePainlessMethod = new PainlessMethod(
2352-
bridgeMethod,
2353-
bridgeClass,
2238+
filteredPainlessMethod = new PainlessMethod(
2239+
painlessMethod.javaMethod(),
2240+
targetClass,
23542241
painlessMethod.returnType(),
2355-
bridgeTypeParameters,
2356-
bridgeHandle,
2357-
bridgeMethodType,
2242+
filteredTypeParameters,
2243+
filteredMethodHandle,
2244+
filteredMethodType,
23582245
Collections.emptyMap()
23592246
);
2360-
painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), bridgePainlessMethod);
2361-
painlessBridgeCache.put(painlessMethod, bridgePainlessMethod);
2247+
painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), filteredPainlessMethod);
2248+
painlessFilteredCache.put(painlessMethod, filteredPainlessMethod);
23622249
} catch (Exception exception) {
23632250
throw new IllegalStateException(
2364-
"internal error occurred attempting to generate a bridge method [" + bridgeClassName + "]",
2251+
"internal error occurred attempting to generate a runtime method [" + painlessMethodKey + "]",
23652252
exception
23662253
);
23672254
}
23682255
} else {
2369-
painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), bridgePainlessMethod);
2256+
painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), filteredPainlessMethod);
23702257
}
23712258
}
23722259

0 commit comments

Comments
 (0)