-
Notifications
You must be signed in to change notification settings - Fork 710
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JEP 384: Record final fields not modifiable through reflection
Related to: #9929 Fix: #9529 - implement restrictions on record final fields through reflection - test that final fields in records cant be modified through reflection - test that jdk.internal.misc.Unsafe methods objectFieldOffset/staticFieldOffset/staticFieldBase support records - test that there is no record support for VarHandle.set Signed-off-by: Theresa Mammarella <Theresa.T.Mammarella@ibm.com>
- Loading branch information
Showing
6 changed files
with
202 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
test/functional/Java15andUp/src/org/openj9/test/records/RecordFinalFieldTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package org.openj9.test.records; | ||
|
||
/******************************************************************************* | ||
* Copyright (c) 2020, 2020 IBM Corp. and others | ||
* | ||
* This program and the accompanying materials are made available under | ||
* the terms of the Eclipse Public License 2.0 which accompanies this | ||
* distribution and is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* or the Apache License, Version 2.0 which accompanies this distribution and | ||
* is available at https://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* This Source Code may also be made available under the following | ||
* Secondary Licenses when the conditions for such availability set | ||
* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU | ||
* General Public License, version 2 with the GNU Classpath | ||
* Exception [1] and GNU General Public License, version 2 with the | ||
* OpenJDK Assembly Exception [2]. | ||
* | ||
* [1] https://www.gnu.org/software/classpath/license.html | ||
* [2] http://openjdk.java.net/legal/assembly-exception.html | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception | ||
*******************************************************************************/ | ||
|
||
import org.testng.annotations.Test; | ||
import org.testng.AssertJUnit; | ||
|
||
import java.lang.invoke.MethodHandles; | ||
import java.lang.invoke.VarHandle; | ||
import java.lang.reflect.Field; | ||
import jdk.internal.misc.Unsafe; | ||
|
||
@Test(groups = { "level.sanity" }) | ||
public class RecordFinalFieldTests { | ||
|
||
static Unsafe unsafe = Unsafe.getUnsafe(); | ||
|
||
record TestRecord(Integer recordComponent) { | ||
static String modifiableField = "old"; | ||
static final String finalField = "old"; | ||
} | ||
|
||
/* All generated record component fields are final and are not modifiable through reflection. */ | ||
@Test(expectedExceptions = java.lang.IllegalAccessException.class) | ||
public void test_javaLangReflectFieldSet_finalInstanceRecordField() throws Throwable { | ||
TestRecord recordClassObject = new TestRecord(0); | ||
Field finalRecordField = recordClassObject.getClass().getDeclaredField("recordComponent"); | ||
finalRecordField.setAccessible(true); | ||
finalRecordField.set(recordClassObject, 5); | ||
} | ||
|
||
/* Final static fields in record classes are not modifiable through reflection. */ | ||
@Test(expectedExceptions = java.lang.IllegalAccessException.class) | ||
public void test_javaLangReflectFieldSet_finalStaticRecordField() throws Throwable { | ||
TestRecord recordClassObject = new TestRecord(0); | ||
Field finalRecordField = recordClassObject.getClass().getDeclaredField("finalField"); | ||
finalRecordField.setAccessible(true); | ||
finalRecordField.set(recordClassObject, "new"); | ||
} | ||
|
||
/* Make sure non-final fields in record classes can still be modified. */ | ||
@Test | ||
public void test_javaLangReflectFieldSet_staticRecordField() throws Throwable { | ||
TestRecord recordClassObject = new TestRecord(0); | ||
Field recordField = recordClassObject.getClass().getDeclaredField("modifiableField"); | ||
recordField.setAccessible(true); | ||
recordField.set(recordClassObject, "new"); | ||
} | ||
|
||
/* Verify behavior of setting final instance field in a record constructor. */ | ||
record TestRecordWithConstructorSettingFinalInstance(Integer recordComponent) { | ||
TestRecordWithConstructorSettingFinalInstance { | ||
recordComponent = 0; | ||
} | ||
} | ||
|
||
@Test | ||
public void test_recordClassConstructor_setFinalInstanceField() { | ||
TestRecordWithConstructorSettingFinalInstance record = new TestRecordWithConstructorSettingFinalInstance(0); | ||
} | ||
|
||
/* Verify behavior of setting final static field in a record constructor. */ | ||
record TestRecordWithConstructorSettingFinalStatic() { | ||
static final String finalField = "old"; | ||
|
||
TestRecordWithConstructorSettingFinalStatic { | ||
try { | ||
Field finalRecordField = this.getClass().getDeclaredField("finalField"); | ||
finalRecordField.setAccessible(true); | ||
finalRecordField.set(this, "new"); | ||
AssertJUnit.fail("No exception was thrown."); | ||
} catch(IllegalAccessException e) { | ||
/* expected exception - test passed*/ | ||
} catch(Throwable e) { | ||
AssertJUnit.fail(e.getMessage()); | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
public void test_recordClassConstructor_setFinalStaticField() { | ||
TestRecordWithConstructorSettingFinalStatic record = new TestRecordWithConstructorSettingFinalStatic(); | ||
} | ||
|
||
/* Verify behavior of setting static field in a record constructor. */ | ||
record TestRecordWithConstructorSettingModifiableStatic() { | ||
static String modifiableField = "old"; | ||
|
||
TestRecordWithConstructorSettingModifiableStatic { | ||
try { | ||
Field finalRecordField = this.getClass().getDeclaredField("modifiableField"); | ||
finalRecordField.setAccessible(true); | ||
finalRecordField.set(this, "new"); | ||
} catch(Throwable e) { | ||
AssertJUnit.fail(e.getMessage()); | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
public void test_recordClassConstructor_setStaticField() { | ||
TestRecordWithConstructorSettingModifiableStatic record = new TestRecordWithConstructorSettingModifiableStatic(); | ||
} | ||
|
||
/* Check that Unsafe.objectFieldOffset supports records. */ | ||
@Test | ||
public void test_jdkInternalMiscUnsafe_objectFieldOffset() throws Throwable { | ||
Field finalRecordField = TestRecord.class.getDeclaredField("recordComponent"); | ||
unsafe.objectFieldOffset(finalRecordField); | ||
} | ||
|
||
/* Check that Unsafe.staticFieldBase supports records. */ | ||
@Test | ||
public void test_jdkInternalMiscUnsafe_staticFieldOffset() throws Throwable { | ||
Field finalRecordField = TestRecord.class.getDeclaredField("finalField"); | ||
unsafe.staticFieldOffset(finalRecordField); | ||
} | ||
|
||
/* Check that Unsafe.staticFieldBase supports records. */ | ||
@Test | ||
public void test_jdkInternalMiscUnsafe_staticFieldBase() throws Throwable { | ||
Field finalRecordField = TestRecord.class.getDeclaredField("finalField"); | ||
unsafe.staticFieldBase(finalRecordField); | ||
} | ||
|
||
/* VarHandle.set is not supported for records. */ | ||
// TODO this should be UnsupportedOperationException - openj9 is throwing WrongMethodTypeException from method handles code which is being replaced. | ||
@Test(expectedExceptions = java.lang.Exception.class) | ||
public void test_javaLangInvokeMethodHandles_unreflectVarHandle_instance() throws Throwable { | ||
Field finalRecordField = TestRecord.class.getDeclaredField("recordComponent"); | ||
finalRecordField.setAccessible(true); | ||
VarHandle finalRecordFieldHandle = MethodHandles.lookup().unreflectVarHandle(finalRecordField); | ||
finalRecordFieldHandle.set(new Integer(5)); | ||
} | ||
|
||
/* VarHandle.set is not supported for records. */ | ||
@Test(expectedExceptions = java.lang.UnsupportedOperationException.class) | ||
public void test_javaLangInvokeMethodHandles_unreflectVarHandle_static() throws Throwable { | ||
Field finalRecordField = TestRecord.class.getDeclaredField("finalField"); | ||
finalRecordField.setAccessible(true); | ||
VarHandle finalRecordFieldHandle = MethodHandles.lookup().unreflectVarHandle(finalRecordField); | ||
finalRecordFieldHandle.set("new"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters