Path: blob/master/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java
41155 views
/*1* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 823050126* @library /test/lib27* @modules java.base/jdk.internal.org.objectweb.asm28* @run testng/othervm ClassDataTest29*/3031import java.io.IOException;32import java.io.OutputStream;33import java.io.UncheckedIOException;34import java.lang.invoke.MethodHandle;35import java.lang.invoke.MethodHandles;36import java.lang.invoke.MethodHandles.Lookup;37import java.lang.invoke.MethodType;38import java.lang.reflect.Method;39import java.nio.file.Files;40import java.nio.file.Path;41import java.nio.file.Paths;42import java.util.ArrayList;43import java.util.List;44import java.util.Map;45import java.util.stream.Stream;4647import jdk.internal.org.objectweb.asm.*;48import org.testng.annotations.DataProvider;49import org.testng.annotations.Test;5051import static java.lang.invoke.MethodHandles.Lookup.*;52import static jdk.internal.org.objectweb.asm.Opcodes.*;53import static org.testng.Assert.*;5455public class ClassDataTest {56private static final Lookup LOOKUP = MethodHandles.lookup();5758@Test59public void testOriginalAccess() throws IllegalAccessException {60Lookup lookup = hiddenClass(20);61assertTrue(lookup.hasFullPrivilegeAccess());6263int value = MethodHandles.classData(lookup, "_", int.class);64assertEquals(value, 20);6566Integer i = MethodHandles.classData(lookup, "_", Integer.class);67assertEquals(i.intValue(), 20);68}6970/*71* A lookup class with no class data.72*/73@Test74public void noClassData() throws IllegalAccessException {75assertNull(MethodHandles.classData(LOOKUP, "_", Object.class));76}7778@DataProvider(name = "teleportedLookup")79private Object[][] teleportedLookup() throws ReflectiveOperationException {80Lookup lookup = hiddenClass(30);81Class<?> hc = lookup.lookupClass();82assertClassData(lookup, 30);8384int fullAccess = PUBLIC|PROTECTED|PACKAGE|MODULE|PRIVATE;85return new Object[][] {86new Object[] { MethodHandles.privateLookupIn(hc, LOOKUP), fullAccess},87new Object[] { LOOKUP.in(hc), fullAccess & ~(PROTECTED|PRIVATE) },88new Object[] { lookup.dropLookupMode(PRIVATE), fullAccess & ~(PROTECTED|PRIVATE) },89};90}9192@Test(dataProvider = "teleportedLookup", expectedExceptions = { IllegalAccessException.class })93public void illegalAccess(Lookup lookup, int access) throws IllegalAccessException {94int lookupModes = lookup.lookupModes();95assertTrue((lookupModes & ORIGINAL) == 0);96assertEquals(lookupModes, access);97MethodHandles.classData(lookup, "_", int.class);98}99100@Test(expectedExceptions = { ClassCastException.class })101public void incorrectType() throws IllegalAccessException {102Lookup lookup = hiddenClass(20);103MethodHandles.classData(lookup, "_", Long.class);104}105106@Test(expectedExceptions = { IndexOutOfBoundsException.class })107public void invalidIndex() throws IllegalAccessException {108Lookup lookup = hiddenClass(List.of());109MethodHandles.classDataAt(lookup, "_", Object.class, 0);110}111112@Test(expectedExceptions = { NullPointerException.class })113public void unboxNull() throws IllegalAccessException {114List<Integer> list = new ArrayList<>();115list.add(null);116Lookup lookup = hiddenClass(list);117MethodHandles.classDataAt(lookup, "_", int.class, 0);118}119120@Test121public void nullElement() throws IllegalAccessException {122List<Object> list = new ArrayList<>();123list.add(null);124Lookup lookup = hiddenClass(list);125assertTrue(MethodHandles.classDataAt(lookup, "_", Object.class, 0) == null);126}127128@Test129public void intClassData() throws ReflectiveOperationException {130ClassByteBuilder builder = new ClassByteBuilder("T1-int");131byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, int.class).build();132Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, 100, true);133int value = MethodHandles.classData(lookup, "_", int.class);134assertEquals(value, 100);135// call through condy136assertClassData(lookup, 100);137}138139@Test140public void floatClassData() throws ReflectiveOperationException {141ClassByteBuilder builder = new ClassByteBuilder("T1-float");142byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, float.class).build();143Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, 0.1234f, true);144float value = MethodHandles.classData(lookup, "_", float.class);145assertEquals(value, 0.1234f);146// call through condy147assertClassData(lookup, 0.1234f);148}149150@Test151public void classClassData() throws ReflectiveOperationException {152Class<?> hc = hiddenClass(100).lookupClass();153ClassByteBuilder builder = new ClassByteBuilder("T2");154byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Class.class).build();155Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, hc, true);156Class<?> value = MethodHandles.classData(lookup, "_", Class.class);157assertEquals(value, hc);158// call through condy159assertClassData(lookup, hc);160}161162@Test163public void arrayClassData() throws ReflectiveOperationException {164ClassByteBuilder builder = new ClassByteBuilder("T3");165byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, String[].class).build();166String[] colors = new String[] { "red", "yellow", "blue"};167Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, colors, true);168assertClassData(lookup, colors.clone());169// class data is modifiable and not a constant170colors[0] = "black";171// it will get back the modified class data172String[] value = MethodHandles.classData(lookup, "_", String[].class);173assertEquals(value, colors);174// even call through condy as it's not a constant175assertClassData(lookup, colors);176}177178@Test179public void listClassData() throws ReflectiveOperationException {180ClassByteBuilder builder = new ClassByteBuilder("T4");181byte[] bytes = builder.classDataAt(ACC_PUBLIC|ACC_STATIC, Integer.class, 2).build();182List<Integer> cd = List.of(100, 101, 102, 103);183int expected = 102; // element at index=2184Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true);185int value = MethodHandles.classDataAt(lookup, "_", int.class, 2);186assertEquals(value, expected);187// call through condy188assertClassData(lookup, expected);189}190191@Test192public void arrayListClassData() throws ReflectiveOperationException {193ClassByteBuilder builder = new ClassByteBuilder("T4");194byte[] bytes = builder.classDataAt(ACC_PUBLIC|ACC_STATIC, Integer.class, 1).build();195ArrayList<Integer> cd = new ArrayList<>();196Stream.of(100, 101, 102, 103).forEach(cd::add);197int expected = 101; // element at index=1198Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true);199int value = MethodHandles.classDataAt(lookup, "_", int.class, 1);200assertEquals(value, expected);201// call through condy202assertClassData(lookup, expected);203}204205private static Lookup hiddenClass(int value) {206ClassByteBuilder builder = new ClassByteBuilder("HC");207byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, int.class).build();208try {209return LOOKUP.defineHiddenClassWithClassData(bytes, value, true);210} catch (Throwable e) {211throw new RuntimeException(e);212}213}214private static Lookup hiddenClass(List<?> list) {215ClassByteBuilder builder = new ClassByteBuilder("HC");216byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, List.class).build();217try {218return LOOKUP.defineHiddenClassWithClassData(bytes, list, true);219} catch (Throwable e) {220throw new RuntimeException(e);221}222}223224@Test225public void condyInvokedFromVirtualMethod() throws ReflectiveOperationException {226ClassByteBuilder builder = new ClassByteBuilder("T5");227// generate classData instance method228byte[] bytes = builder.classData(ACC_PUBLIC, Class.class).build();229Lookup hcLookup = hiddenClass(100);230assertClassData(hcLookup, 100);231Class<?> hc = hcLookup.lookupClass();232Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, hc, true);233Class<?> value = MethodHandles.classData(lookup, "_", Class.class);234assertEquals(value, hc);235// call through condy236Class<?> c = lookup.lookupClass();237assertClassData(lookup, c.newInstance(), hc);238}239240@Test241public void immutableListClassData() throws ReflectiveOperationException {242ClassByteBuilder builder = new ClassByteBuilder("T6");243// generate classDataAt instance method244byte[] bytes = builder.classDataAt(ACC_PUBLIC, Integer.class, 2).build();245List<Integer> cd = List.of(100, 101, 102, 103);246int expected = 102; // element at index=2247Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true);248int value = MethodHandles.classDataAt(lookup, "_", int.class, 2);249assertEquals(value, expected);250// call through condy251Class<?> c = lookup.lookupClass();252assertClassData(lookup, c.newInstance() ,expected);253}254255/*256* The return value of MethodHandles::classDataAt is the element257* contained in the list when the method is called.258* If MethodHandles::classDataAt is called via condy, the value259* will be captured as a constant. If the class data is modified260* after the element at the given index is computed via condy,261* subsequent LDC of such ConstantDynamic entry will return the same262* value. However, direct invocation of MethodHandles::classDataAt263* will return the modified value.264*/265@Test266public void mutableListClassData() throws ReflectiveOperationException {267ClassByteBuilder builder = new ClassByteBuilder("T7");268// generate classDataAt instance method269byte[] bytes = builder.classDataAt(ACC_PUBLIC, MethodType.class, 0).build();270MethodType mtype = MethodType.methodType(int.class, String.class);271List<MethodType> cd = new ArrayList<>(List.of(mtype));272Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true);273// call through condy274Class<?> c = lookup.lookupClass();275assertClassData(lookup, c.newInstance(), mtype);276// modify the class data277assertTrue(cd.remove(0) == mtype);278cd.add(0, MethodType.methodType(void.class));279MethodType newMType = cd.get(0);280// loading the element using condy returns the original value281assertClassData(lookup, c.newInstance(), mtype);282// direct invocation of MethodHandles.classDataAt returns the modified value283assertEquals(MethodHandles.classDataAt(lookup, "_", MethodType.class, 0), newMType);284}285286// helper method to extract from a class data map287public static <T> T getClassDataEntry(Lookup lookup, String key, Class<T> type) throws IllegalAccessException {288Map<String, T> cd = MethodHandles.classData(lookup, "_", Map.class);289return type.cast(cd.get(key));290}291292@Test293public void classDataMap() throws ReflectiveOperationException {294ClassByteBuilder builder = new ClassByteBuilder("map");295// generate classData static method296Handle bsm = new Handle(H_INVOKESTATIC, "ClassDataTest", "getClassDataEntry",297"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;",298false);299// generate two accessor methods to get the entries from class data300byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Map.class)301.classData(ACC_PUBLIC|ACC_STATIC, "getClass",302Class.class, new ConstantDynamic("class", Type.getDescriptor(Class.class), bsm))303.classData(ACC_PUBLIC|ACC_STATIC, "getMethod",304MethodHandle.class, new ConstantDynamic("method", Type.getDescriptor(MethodHandle.class), bsm))305.build();306307// generate a hidden class308Lookup hcLookup = hiddenClass(100);309Class<?> hc = hcLookup.lookupClass();310assertClassData(hcLookup, 100);311312MethodHandle mh = hcLookup.findStatic(hc, "classData", MethodType.methodType(int.class));313Map<String, Object> cd = Map.of("class", hc, "method", mh);314Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true);315assertClassData(lookup, cd);316317// validate the entries from the class data map318Class<?> c = lookup.lookupClass();319Method m = c.getMethod("getClass");320Class<?> v = (Class<?>)m.invoke(null);321assertEquals(hc, v);322323Method m1 = c.getMethod("getMethod");324MethodHandle v1 = (MethodHandle) m1.invoke(null);325assertEquals(mh, v1);326}327328@Test(expectedExceptions = { IllegalArgumentException.class })329public void nonDefaultName() throws ReflectiveOperationException {330ClassByteBuilder builder = new ClassByteBuilder("nonDefaultName");331byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Class.class)332.build();333Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, ClassDataTest.class, true);334assertClassData(lookup, ClassDataTest.class);335// throw IAE336MethodHandles.classData(lookup, "non_default_name", Class.class);337}338339static class ClassByteBuilder {340private static final String OBJECT_CLS = "java/lang/Object";341private static final String MHS_CLS = "java/lang/invoke/MethodHandles";342private static final String CLASS_DATA_BSM_DESCR =343"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;";344private final ClassWriter cw;345private final String classname;346347/**348* A builder to generate a class file to access class data349* @param classname350*/351ClassByteBuilder(String classname) {352this.classname = classname;353this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);354cw.visit(V14, ACC_FINAL, classname, null, OBJECT_CLS, null);355MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);356mv.visitCode();357mv.visitVarInsn(ALOAD, 0);358mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "<init>", "()V", false);359mv.visitInsn(RETURN);360mv.visitMaxs(0, 0);361mv.visitEnd();362}363364byte[] build() {365cw.visitEnd();366byte[] bytes = cw.toByteArray();367Path p = Paths.get(classname + ".class");368try (OutputStream os = Files.newOutputStream(p)) {369os.write(bytes);370} catch (IOException e) {371throw new UncheckedIOException(e);372}373return bytes;374}375376/*377* Generate classData method to load class data via condy378*/379ClassByteBuilder classData(int accessFlags, Class<?> returnType) {380MethodType mtype = MethodType.methodType(returnType);381MethodVisitor mv = cw.visitMethod(accessFlags,382"classData",383mtype.descriptorString(), null, null);384mv.visitCode();385Handle bsm = new Handle(H_INVOKESTATIC, MHS_CLS, "classData",386CLASS_DATA_BSM_DESCR,387false);388ConstantDynamic dynamic = new ConstantDynamic("_", Type.getDescriptor(returnType), bsm);389mv.visitLdcInsn(dynamic);390mv.visitInsn(returnType == int.class ? IRETURN :391(returnType == float.class ? FRETURN : ARETURN));392mv.visitMaxs(0, 0);393mv.visitEnd();394return this;395}396397/*398* Generate classDataAt method to load an element from class data via condy399*/400ClassByteBuilder classDataAt(int accessFlags, Class<?> returnType, int index) {401MethodType mtype = MethodType.methodType(returnType);402MethodVisitor mv = cw.visitMethod(accessFlags,403"classData",404mtype.descriptorString(), null, null);405mv.visitCode();406Handle bsm = new Handle(H_INVOKESTATIC, "java/lang/invoke/MethodHandles", "classDataAt",407"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;I)Ljava/lang/Object;",408false);409ConstantDynamic dynamic = new ConstantDynamic("_", Type.getDescriptor(returnType), bsm, index);410mv.visitLdcInsn(dynamic);411mv.visitInsn(returnType == int.class? IRETURN : ARETURN);412mv.visitMaxs(0, 0);413mv.visitEnd();414return this;415}416417ClassByteBuilder classData(int accessFlags, String name, Class<?> returnType, ConstantDynamic dynamic) {418MethodType mtype = MethodType.methodType(returnType);419MethodVisitor mv = cw.visitMethod(accessFlags,420name,421mtype.descriptorString(), null, null);422mv.visitCode();423mv.visitLdcInsn(dynamic);424mv.visitInsn(returnType == int.class? IRETURN : ARETURN);425mv.visitMaxs(0, 0);426mv.visitEnd();427return this;428}429}430431/*432* Load an int constant from class data via condy and433* verify it matches the given value.434*/435private void assertClassData(Lookup lookup, int value) throws ReflectiveOperationException {436Class<?> c = lookup.lookupClass();437Method m = c.getMethod("classData");438int v = (int) m.invoke(null);439assertEquals(value, v);440}441442/*443* Load an int constant from class data via condy and444* verify it matches the given value.445*/446private void assertClassData(Lookup lookup, Object o, int value) throws ReflectiveOperationException {447Class<?> c = lookup.lookupClass();448Method m = c.getMethod("classData");449int v = (int) m.invoke(o);450assertEquals(value, v);451}452453/*454* Load a float constant from class data via condy and455* verify it matches the given value.456*/457private void assertClassData(Lookup lookup, float value) throws ReflectiveOperationException {458Class<?> c = lookup.lookupClass();459Method m = c.getMethod("classData");460float v = (float) m.invoke(null);461assertEquals(value, v);462}463464/*465* Load a Class constant from class data via condy and466* verify it matches the given value.467*/468private void assertClassData(Lookup lookup, Class<?> value) throws ReflectiveOperationException {469Class<?> c = lookup.lookupClass();470Method m = c.getMethod("classData");471Class<?> v = (Class<?>)m.invoke(null);472assertEquals(value, v);473}474475/*476* Load a Class from class data via condy and477* verify it matches the given value.478*/479private void assertClassData(Lookup lookup, Object o, Class<?> value) throws ReflectiveOperationException {480Class<?> c = lookup.lookupClass();481Method m = c.getMethod("classData");482Object v = m.invoke(o);483assertEquals(value, v);484}485486/*487* Load an Object from class data via condy and488* verify it matches the given value.489*/490private void assertClassData(Lookup lookup, Object value) throws ReflectiveOperationException {491Class<?> c = lookup.lookupClass();492Method m = c.getMethod("classData");493Object v = m.invoke(null);494assertEquals(value, v);495}496497/*498* Load an Object from class data via condy and499* verify it matches the given value.500*/501private void assertClassData(Lookup lookup, Object o, Object value) throws ReflectiveOperationException {502Class<?> c = lookup.lookupClass();503Method m = c.getMethod("classData");504Object v = m.invoke(o);505assertEquals(value, v);506}507}508509510511512