8220282: Add MethodHandle tests on accessing final fields

Reviewed-by: lancea
This commit is contained in:
Mandy Chung 2019-04-06 21:05:58 +08:00
parent 0abdc381b7
commit ab361746ec
2 changed files with 123 additions and 58 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -635,7 +635,7 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
public void testGetter(int testMode) throws Throwable {
Lookup lookup = PRIVATE; // FIXME: test more lookups than this one
for (Object[] c : HasFields.CASES) {
for (Object[] c : HasFields.testCasesFor(testMode)) {
boolean positive = (c[1] != Error.class);
testGetter(positive, lookup, c[0], c[1], testMode);
if (positive)
@ -665,7 +665,6 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
boolean testNPE = ((testMode0 & TEST_NPE) != 0);
int testMode = testMode0 & ~(TEST_SETTER | TEST_BOUND | TEST_NPE);
boolean positive = positive0 && !testNPE;
boolean isFinal;
boolean isStatic;
Class<?> fclass;
String fname;
@ -673,14 +672,12 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
Field f = (fieldRef instanceof Field ? (Field)fieldRef : null);
if (f != null) {
isStatic = Modifier.isStatic(f.getModifiers());
isFinal = Modifier.isFinal(f.getModifiers());
fclass = f.getDeclaringClass();
fname = f.getName();
ftype = f.getType();
} else {
Object[] scnt = (Object[]) fieldRef;
isStatic = (Boolean) scnt[0];
isFinal = false;
fclass = (Class<?>) scnt[1];
fname = (String) scnt[2];
ftype = (Class<?>) scnt[3];
@ -724,19 +721,21 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
? NoSuchFieldException.class
: IllegalAccessException.class,
noAccess);
if (((testMode0 & TEST_SETTER) != 0) && (isFinal && isStatic)) return; // Final static field setter test failed as intended.
if (verbosity >= 5) ex.printStackTrace(System.out);
}
if (verbosity >= 3)
System.out.println((((testMode0 & TEST_UNREFLECT) != 0)?"unreflect":"find")
+(((testMode0 & TEST_FIND_STATIC) != 0)?"Static":"")
+(isGetter?"Getter":"Setter")
+" "+fclass.getName()+"."+fname+"/"+ftype
+" => "+mh
+(noAccess == null ? "" : " !! "+noAccess));
System.out.format("%s%s %s.%s/%s => %s %s%n",
(testMode0 & TEST_UNREFLECT) != 0
? "unreflect"
: "find" + ((testMode0 & TEST_FIND_STATIC) != 0 ? "Static" : ""),
(isGetter ? "Getter" : "Setter"),
fclass.getName(), fname, ftype, mh,
(noAccess == null ? "" : " !! "+noAccess));
// negative test case and expected noAccess, then done.
if (!positive && noAccess != null) return;
// positive test case but found noAccess, then error
if (positive && !testNPE && noAccess != null) throw new RuntimeException(noAccess);
assertEquals(positive0 ? "positive test" : "negative test erroneously passed", positive0, mh != null);
assertFalse("Setter methods should throw an exception if passed a final static field.", ((testMode0 & TEST_SETTER) != 0) && (isFinal && isStatic));
if (!positive && !testNPE) return; // negative access test failed as expected
assertEquals((isStatic ? 0 : 1)+(isGetter ? 0 : 1), mh.type().parameterCount());
assertSame(mh.type(), expType);
@ -762,6 +761,9 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
assertEquals(f.get(fields), value); // clean to start with
}
Throwable caughtEx = null;
// non-final field and setAccessible(true) on instance field will have write access
boolean writeAccess = !Modifier.isFinal(f.getModifiers()) ||
(!Modifier.isStatic(f.getModifiers()) && f.isAccessible());
if (isGetter) {
Object expValue = value;
for (int i = 0; i <= 1; i++) {
@ -785,7 +787,7 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
}
}
assertEquals(sawValue, expValue);
if (f != null && f.getDeclaringClass() == HasFields.class && !isFinal) {
if (f != null && f.getDeclaringClass() == HasFields.class && writeAccess) {
Object random = randomArg(ftype);
f.set(fields, random);
expValue = random;
@ -819,8 +821,8 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
}
}
}
if ((f != null) && (f.getDeclaringClass() == HasFields.class) && !isFinal) {
f.set(fields, value); // put it back if we changed it.
if (f != null && f.getDeclaringClass() == HasFields.class && writeAccess) {
f.set(fields, value); // put it back if it has write access
}
if (testNPE) {
if (caughtEx == null || !(caughtEx instanceof NullPointerException))
@ -868,7 +870,8 @@ public class MethodHandlesGeneralTest extends MethodHandlesTest {
public void testSetter(int testMode) throws Throwable {
Lookup lookup = PRIVATE; // FIXME: test more lookups than this one
for (Object[] c : HasFields.CASES) {
startTest("testSetter");
for (Object[] c : HasFields.testCasesFor(testMode|TEST_SETTER)) {
boolean positive = (c[1] != Error.class);
testSetter(positive, lookup, c[0], c[1], testMode);
if (positive)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -38,6 +38,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.Assert.*;
@ -561,25 +562,37 @@ public abstract class MethodHandlesTest {
static long sJ = 1+'J';
static float sF = 1+'F';
static double sD = 1+'D';
// final fields
final boolean fiZ = false;
final byte fiB = 2+(byte)'B';
final short fiS = 2+(short)'S';
final char fiC = 2+'C';
final int fiI = 2+'I';
final long fiJ = 2+'J';
final float fiF = 2+'F';
final double fiD = 2+'D';
final static boolean fsZ = false;
final static byte fsB = 2+(byte)'B';
final static short fsS = 2+(short)'S';
final static char fsC = 2+'C';
final static int fsI = 2+'I';
final static long fsJ = 2+'J';
final static float fsF = 2+'F';
final static double fsD = 2+'D';
final static byte fsB = 3+(byte)'B';
final static short fsS = 3+(short)'S';
final static char fsC = 3+'C';
final static int fsI = 3+'I';
final static long fsJ = 3+'J';
final static float fsF = 3+'F';
final static double fsD = 3+'D';
Object iL = 'L';
String iR = "R";
static Object sL = 'M';
static String sR = "S";
final static Object fsL = 'N';
final static String fsR = "T";
String iR = "iR";
static Object sL = 1+'L';
static String sR = "sR";
final Object fiL = 2+'L';
final String fiR = "fiR";
final static Object fsL = 3+'L';
final static String fsR = "fsR";
static final Object[][] CASES;
static final ArrayList<Object[]> STATIC_FIELD_CASES = new ArrayList<>();
static final ArrayList<Object[]> INSTANCE_FIELD_CASES = new ArrayList<>();
static {
ArrayList<Object[]> cases = new ArrayList<>();
Object types[][] = {
{'L',Object.class}, {'R',String.class},
{'I',int.class}, {'J',long.class},
@ -589,42 +602,91 @@ public abstract class MethodHandlesTest {
};
HasFields fields = new HasFields();
for (Object[] t : types) {
for (int kind = 0; kind <= 2; kind++) {
for (int kind = 0; kind <= 1; kind++) {
boolean isStatic = (kind != 0);
boolean isFinal = (kind == 2);
ArrayList<Object[]> cases = isStatic ? STATIC_FIELD_CASES : INSTANCE_FIELD_CASES;
char btc = (Character)t[0];
String name = (isStatic ? "s" : "i") + btc;
if (isFinal) name = "f" + name;
String fname = (isStatic ? "s" : "i") + btc;
String finalFname = (isStatic ? "fs" : "fi") + btc;
Class<?> type = (Class<?>) t[1];
Object value;
Field field;
try {
field = HasFields.class.getDeclaredField(name);
} catch (NoSuchFieldException | SecurityException ex) {
throw new InternalError("no field HasFields."+name);
}
try {
value = field.get(fields);
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new InternalError("cannot fetch field HasFields."+name);
}
// non-final field
Field nonFinalField = getField(fname, type);
Object value = getValue(fields, nonFinalField);
if (type == float.class) {
float v = 'F';
if (isStatic) v++;
if (isFinal) v++;
assertTrue(value.equals(v));
}
if (isFinal && isStatic) field.setAccessible(true);
assertTrue(name.equals(field.getName()));
assertTrue(type.equals(field.getType()));
assertTrue(isStatic == (Modifier.isStatic(field.getModifiers())));
assertTrue(isFinal == (Modifier.isFinal(field.getModifiers())));
cases.add(new Object[]{ field, value });
assertTrue(isStatic == (Modifier.isStatic(nonFinalField.getModifiers())));
cases.add(new Object[]{ nonFinalField, value });
// setAccessible(true) on final field but static final field only has read access
Field finalField = getField(finalFname, type);
Object fvalue = getValue(fields, finalField);
finalField.setAccessible(true);
assertTrue(isStatic == (Modifier.isStatic(finalField.getModifiers())));
cases.add(new Object[]{ finalField, fvalue, Error.class});
}
}
cases.add(new Object[]{ new Object[]{ false, HasFields.class, "bogus_fD", double.class }, Error.class });
cases.add(new Object[]{ new Object[]{ true, HasFields.class, "bogus_sL", Object.class }, Error.class });
CASES = cases.toArray(new Object[0][]);
INSTANCE_FIELD_CASES.add(new Object[]{ new Object[]{ false, HasFields.class, "bogus_fD", double.class }, Error.class });
STATIC_FIELD_CASES.add(new Object[]{ new Object[]{ true, HasFields.class, "bogus_sL", Object.class }, Error.class });
}
private static Field getField(String name, Class<?> type) {
try {
Field field = HasFields.class.getDeclaredField(name);
assertTrue(name.equals(field.getName()));
assertTrue(type.equals(field.getType()));
return field;
} catch (NoSuchFieldException | SecurityException ex) {
throw new InternalError("no field HasFields."+name);
}
}
private static Object getValue(Object o, Field field) {
try {
return field.get(o);
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new InternalError("cannot fetch field HasFields."+field.getName());
}
}
static Object[][] testCasesFor(int testMode) {
Stream<Object[]> cases;
if ((testMode & TEST_UNREFLECT) != 0) {
cases = Stream.concat(STATIC_FIELD_CASES.stream(), INSTANCE_FIELD_CASES.stream());
} else if ((testMode & TEST_FIND_STATIC) != 0) {
cases = STATIC_FIELD_CASES.stream();
} else if ((testMode & TEST_FIND_FIELD) != 0) {
cases = INSTANCE_FIELD_CASES.stream();
} else {
throw new InternalError("unexpected test mode: " + testMode);
}
return cases.map(c -> mapTestCase(testMode, c)).toArray(Object[][]::new);
}
private static Object[] mapTestCase(int testMode, Object[] c) {
// non-final fields (2-element) and final fields (3-element) if not TEST_SETTER
if (c.length == 2 || (testMode & TEST_SETTER) == 0)
return c;
// final fields (3-element)
assertTrue((testMode & TEST_SETTER) != 0 && c[0] instanceof Field && c[2] == Error.class);
if ((testMode & TEST_UNREFLECT) == 0)
return new Object[]{ c[0], c[2]}; // negative test case; can't set on final fields
// unreflectSetter grants write access on instance final field if accessible flag is true
// hence promote the negative test case to positive test case
Field f = (Field) c[0];
int mods = f.getModifiers();
if (!Modifier.isFinal(mods) || (!Modifier.isStatic(mods) && f.isAccessible())) {
// positive test case
return new Object[]{ c[0], c[1] };
} else {
// otherwise, negative test case
return new Object[]{ c[0], c[2]};
}
}
}