package org.elasticsearch.painless; /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import org.elasticsearch.test.ESTestCase; public class DefBootstrapTests extends ESTestCase { private final Definition definition = Definition.BUILTINS; /** calls toString() on integers, twice */ public void testOneType() throws Throwable { CallSite site = DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "toString", MethodType.methodType(String.class, Object.class), 0, DefBootstrap.METHOD_CALL, ""); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); // invoke with integer, needs lookup assertEquals("5", (String)handle.invokeExact((Object)5)); assertDepthEquals(site, 1); // invoked with integer again: should be cached assertEquals("6", (String)handle.invokeExact((Object)6)); assertDepthEquals(site, 1); } public void testTwoTypes() throws Throwable { CallSite site = DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "toString", MethodType.methodType(String.class, Object.class), 0, DefBootstrap.METHOD_CALL, ""); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); assertEquals("5", (String)handle.invokeExact((Object)5)); assertDepthEquals(site, 1); assertEquals("1.5", (String)handle.invokeExact((Object)1.5f)); assertDepthEquals(site, 2); // both these should be cached assertEquals("6", (String)handle.invokeExact((Object)6)); assertDepthEquals(site, 2); assertEquals("2.5", (String)handle.invokeExact((Object)2.5f)); assertDepthEquals(site, 2); } public void testTooManyTypes() throws Throwable { // if this changes, test must be rewritten assertEquals(5, DefBootstrap.PIC.MAX_DEPTH); CallSite site = DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "toString", MethodType.methodType(String.class, Object.class), 0, DefBootstrap.METHOD_CALL, ""); MethodHandle handle = site.dynamicInvoker(); assertDepthEquals(site, 0); assertEquals("5", (String)handle.invokeExact((Object)5)); assertDepthEquals(site, 1); assertEquals("1.5", (String)handle.invokeExact((Object)1.5f)); assertDepthEquals(site, 2); assertEquals("6", (String)handle.invokeExact((Object)6L)); assertDepthEquals(site, 3); assertEquals("3.2", (String)handle.invokeExact((Object)3.2d)); assertDepthEquals(site, 4); assertEquals("foo", (String)handle.invokeExact((Object)"foo")); assertDepthEquals(site, 5); assertEquals("c", (String)handle.invokeExact((Object)'c')); assertDepthEquals(site, 5); } /** test that we revert to the megamorphic classvalue cache and that it works as expected */ public void testMegamorphic() throws Throwable { DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "size", MethodType.methodType(int.class, Object.class), 0, DefBootstrap.METHOD_CALL, ""); site.depth = DefBootstrap.PIC.MAX_DEPTH; // mark megamorphic MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (int)handle.invokeExact((Object) Arrays.asList("1", "2"))); assertEquals(1, (int)handle.invokeExact((Object) Collections.singletonMap("a", "b"))); assertEquals(3, (int)handle.invokeExact((Object) Arrays.asList("x", "y", "z"))); assertEquals(2, (int)handle.invokeExact((Object) Arrays.asList("u", "v"))); final HashMap<String,String> map = new HashMap<String,String>(); map.put("x", "y"); map.put("a", "b"); assertEquals(2, (int)handle.invokeExact((Object) map)); final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> { Integer.toString((int)handle.invokeExact(new Object())); }); assertEquals("Unable to find dynamic method [size] with [0] arguments for class [java.lang.Object].", iae.getMessage()); assertTrue("Does not fail inside ClassValue.computeValue()", Arrays.stream(iae.getStackTrace()).anyMatch(e -> { return e.getMethodName().equals("computeValue") && e.getClassName().startsWith("org.elasticsearch.painless.DefBootstrap$PIC$"); })); } // test operators with null guards public void testNullGuardAdd() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, Object.class, Object.class), 0, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test")); } public void testNullGuardAddWhenCached() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, Object.class, Object.class), 0, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (Object)handle.invokeExact((Object)1, (Object)1)); assertEquals("nulltest", (Object)handle.invokeExact((Object)null, (Object)"test")); } public void testNullGuardEq() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "eq", MethodType.methodType(boolean.class, Object.class, Object.class), 0, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test")); assertTrue((boolean) handle.invokeExact((Object)null, (Object)null)); } public void testNullGuardEqWhenCached() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "eq", MethodType.methodType(boolean.class, Object.class, Object.class), 0, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL); MethodHandle handle = site.dynamicInvoker(); assertTrue((boolean) handle.invokeExact((Object)1, (Object)1)); assertFalse((boolean) handle.invokeExact((Object)null, (Object)"test")); assertTrue((boolean) handle.invokeExact((Object)null, (Object)null)); } // make sure these operators work without null guards too // for example, nulls are only legal for + if the other parameter is a String, // and can be disabled in some circumstances. public void testNoNullGuardAdd() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, int.class, Object.class), 0, DefBootstrap.BINARY_OPERATOR, 0); MethodHandle handle = site.dynamicInvoker(); expectThrows(NullPointerException.class, () -> { assertNotNull((Object)handle.invokeExact(5, (Object)null)); }); } public void testNoNullGuardAddWhenCached() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(definition, MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, int.class, Object.class), 0, DefBootstrap.BINARY_OPERATOR, 0); MethodHandle handle = site.dynamicInvoker(); assertEquals(2, (Object)handle.invokeExact(1, (Object)1)); expectThrows(NullPointerException.class, () -> { assertNotNull((Object)handle.invokeExact(5, (Object)null)); }); } static void assertDepthEquals(CallSite site, int expected) { DefBootstrap.PIC dsite = (DefBootstrap.PIC) site; assertEquals(expected, dsite.depth); } }