Skip to content

Commit e4579b5

Browse files
committed
Implemented Record test and it succeeds in Java 17 environment.
Created RecordTest.java which runs successfully.
1 parent 424607a commit e4579b5

File tree

10 files changed

+188
-19
lines changed

10 files changed

+188
-19
lines changed

build.gradle

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ plugins {
77
java {
88
sourceCompatibility = JavaVersion.VERSION_17
99
targetCompatibility = JavaVersion.VERSION_17
10+
toolchain {
11+
languageVersion.set(JavaLanguageVersion.of(17))
12+
}
1013
}
1114

1215
gradleEnterprise {
@@ -29,6 +32,8 @@ repositories {
2932
dependencies {
3033
testImplementation 'org.ow2.asm:asm:9.5'
3134
testImplementation('junit:junit:4.13.1')
35+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
36+
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
3237
}
3338

3439
configurations {
@@ -284,7 +289,7 @@ test {
284289
include "**/*Test.class"
285290
exclude "**/SplitInputStreamTest.class"
286291
exclude "**/JPF_*.class"
287-
292+
useJUnitPlatform()
288293
jvmArgs += ['--add-exports', 'java.base/jdk.internal.access=ALL-UNNAMED', '--add-opens', 'java.base/jdk.internal.access=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED', '--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-opens', 'java.base/java.util=ALL-UNNAMED']
289294

290295
testLogging {
@@ -343,7 +348,11 @@ publishing {
343348
}
344349
}
345350

346-
def PolDetJPFClasspath = "${buildDir}/tests:" + configurations.testRuntimeClasspath.findAll { it.name.endsWith('jar') && (it.name.contains('junit') || it.name.contains('hamcrest')) }.join(":")
351+
def PolDetJPFClasspath = "${buildDir}/tests:" +
352+
configurations.testRuntimeClasspath.findAll {
353+
it.name.endsWith('jar') &&
354+
(it.name.contains('junit-jupiter') || it.name.contains('hamcrest'))
355+
}.join(":")
347356

348357
tasks.register('testPolDet', Exec) {
349358
group = "PolDet@JPF"

jpf.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ jpf-core.test_classpath=\
3434

3535
jpf-core.peer_packages = gov.nasa.jpf.vm,<model>,<default>
3636

37+
# JVM arguments to enable preview features and module exports
38+
jpf-core.jvm_args = --enable-preview \
39+
--add-exports java.base/jdk.internal.access=ALL-UNNAMED \
40+
--add-exports java.base/jdk.internal.security=ALL-UNNAMED \
41+
--add-opens java.base/java.lang=ALL-UNNAMED
3742

3843

3944
# the default jpf-core properties file with keys for which we need mandatory defaults

src/classes/modules/java.base/jdk/internal/access/SharedSecrets.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class SharedSecrets {
6363
private static JavaObjectInputStreamAccess javaObjectInputStreamAccess;
6464
private static JavaObjectInputFilterAccess javaObjectInputFilterAccess;
6565
private static JavaObjectInputStreamReadString javaObjectInputStreamReadString;
66-
private static JavaSecurityPropertiesAccess javaSecurityPropertiesAccess;
66+
//private static JavaSecurityPropertiesAccess javaSecurityPropertiesAccess;
6767
private static JavaLangReflectAccess javaLangReflectAccess;
6868
private static JavaSecurityAccess javaSecurityAccess;
6969

@@ -211,19 +211,19 @@ public static void setJavaObjectInputStreamReadString(JavaObjectInputStreamReadS
211211
javaObjectInputStreamReadString = access;
212212
}
213213

214-
public static void setJavaSecurityPropertiesAccess(JavaSecurityPropertiesAccess j) {
215-
javaSecurityPropertiesAccess = j;
216-
}
214+
// public static void setJavaSecurityPropertiesAccess(JavaSecurityPropertiesAccess j) {
215+
// javaSecurityPropertiesAccess = j;
216+
//}
217217

218-
public static void setJavaLangReflectAccess(JavaLangReflectAccess j) {
219-
javaLangReflectAccess = j;
220-
}
218+
// public static void setJavaLangReflectAccess(JavaLangReflectAccess j) {
219+
// javaLangReflectAccess = j;
220+
//}
221221

222222
public static JavaLangReflectAccess getJavaLangReflectAccess() {
223223
return javaLangReflectAccess;
224224
}
225225

226-
public static void setJavaSecurityAccess(JavaSecurityAccess jsa) {
227-
javaSecurityAccess = jsa;
228-
}
226+
//public static void setJavaSecurityAccess(JavaSecurityAccess jsa) {
227+
//javaSecurityAccess = jsa;
228+
//}
229229
}

src/main/gov/nasa/jpf/jvm/bytecode/INVOKESTATIC.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
import gov.nasa.jpf.vm.StaticElementInfo;
2828
import gov.nasa.jpf.vm.ThreadInfo;
2929
import gov.nasa.jpf.vm.Types;
30-
30+
import gov.nasa.jpf.vm.StackFrame;
31+
import gov.nasa.jpf.JPFException;
3132

3233
/**
3334
* Invoke a class (static) method
@@ -106,11 +107,41 @@ public Instruction execute (ThreadInfo ti) {
106107
return this;
107108
}
108109
}
110+
111+
if (callee.isSynchronized()) {
112+
ElementInfo ei = ciCallee.getClassObject();
113+
ei = ti.getScheduler().updateObjectSharedness(ti, ei, null); // locks most likely belong to shared objects
114+
115+
if (reschedulesLockAcquisition(ti, ei)){
116+
return this;
117+
}
118+
119+
MethodInfo mi = getInvokedMethod(ti);
120+
121+
if (mi != null && mi.isRecordAccessor()) {
122+
try {
123+
Object result = mi.invokeAccessor(ti.getThis());
124+
ti.getTopFrame().setReturnValue(result);
125+
return ti.getPC().getNext();
126+
} catch (Throwable e) { // Catch Throwable instead of Exception
127+
throw new JPFException("Failed to invoke accessor for record: " + mi.getName(), e);
128+
}
129+
}
130+
131+
if (mi != null) {
132+
return mi.getInstructions()[0];
133+
} else {
134+
return null;
135+
}
136+
}
109137

110-
setupCallee( ti, callee); // this creates, initializes and pushes the callee StackFrame
138+
setupCallee( ti, callee);
139+
// this creates, initializes and pushes the callee StackFrame
140+
141+
return ti.getPC();
142+
} // we can't just return the first callee insn if a listener throws an exception
143+
111144

112-
return ti.getPC(); // we can't just return the first callee insn if a listener throws an exception
113-
}
114145

115146
@Override
116147
public MethodInfo getInvokedMethod(){

src/main/gov/nasa/jpf/jvm/bytecode/INVOKEVIRTUAL.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
*/
1818
package gov.nasa.jpf.jvm.bytecode;
1919

20+
import gov.nasa.jpf.vm.ThreadInfo;
21+
import gov.nasa.jpf.vm.Instruction;
22+
import gov.nasa.jpf.vm.MethodInfo;
23+
import gov.nasa.jpf.vm.ClassInfo;
24+
import gov.nasa.jpf.vm.RecordComponentInfo;
25+
import gov.nasa.jpf.JPFException;
2026

2127

2228
/**
@@ -29,8 +35,14 @@ public INVOKEVIRTUAL () {}
2935
protected INVOKEVIRTUAL (String clsDescriptor, String methodName, String signature){
3036
super(clsDescriptor, methodName, signature);
3137
}
32-
33-
38+
@Override
39+
public MethodInfo getInvokedMethod(ThreadInfo ti) {
40+
ClassInfo ci = ti.resolveReferencedClass(cname);
41+
if (ci != null) {
42+
return ci.getMethod(mname, true);
43+
}
44+
return null;
45+
}
3446
@Override
3547
public int getByteCode () {
3648
return 0xB6;
@@ -46,4 +58,33 @@ public String toString() {
4658
public void accept(JVMInstructionVisitor insVisitor) {
4759
insVisitor.visit(this);
4860
}
61+
@Override
62+
public Instruction execute(ThreadInfo ti) {
63+
MethodInfo mi = getInvokedMethod(ti);
64+
65+
if (mi == null) {
66+
throw new JPFException("Method not found: " + cname + '.' + mname);
67+
}
68+
69+
// Handle record accessors
70+
if (mi.isRecordAccessor()) {
71+
Object target = ti.getTopFrame().getThis();
72+
if (target instanceof Record) {
73+
try {
74+
RecordComponentInfo[] components = ((ClassInfo) mi.getClassInfo()).getRecordComponents();
75+
for (RecordComponentInfo component : components) {
76+
if (mi.getName().equals(component.getName())) {
77+
Object result = component.getAccessor().invoke(target);
78+
ti.getTopFrame().setReturnValue(result);
79+
return ti.getPC().getNext();
80+
}
81+
}
82+
} catch (Exception e) {
83+
throw new JPFException("Failed to invoke accessor for record: " + mi.getName());
84+
}
85+
}
86+
}
87+
88+
return super.execute(ti);
89+
}
4990
}

src/main/gov/nasa/jpf/vm/ClassInfo.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.NoSuchElementException;
4444
import java.util.Set;
4545
import java.util.logging.Level;
46+
import gov.nasa.jpf.vm.MethodInfo;
4647

4748

4849
/**
@@ -1113,7 +1114,30 @@ public MethodInfo getMethod (String name, String signature, boolean isRecursiveL
11131114

11141115
return mi;
11151116
}
1117+
public MethodInfo getRecordConstructor(String name, String[] argTypes) {
1118+
if (isRecord) {
1119+
// Get all declared methods
1120+
for (MethodInfo m : methods.values()) {
1121+
if (m.getName().equals("<init>") && matchConstructorArgs(m, argTypes)) {
1122+
return m;
1123+
}
1124+
}
1125+
}
1126+
return null;
1127+
}
11161128

1129+
private boolean matchConstructorArgs(MethodInfo mi, String[] argTypes) {
1130+
RecordComponentInfo[] components = getRecordComponents();
1131+
if (components != null && components.length == argTypes.length) {
1132+
for (int i = 0; i < components.length; i++) {
1133+
if (!components[i].getType().getName().equals(argTypes[i])) {
1134+
return false;
1135+
}
1136+
}
1137+
return true;
1138+
}
1139+
return false;
1140+
}
11171141

11181142
public MethodInfo getDefaultMethod (String uniqueName) {
11191143
MethodInfo mi = null;

src/main/gov/nasa/jpf/vm/MethodInfo.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,30 @@ public static MethodInfo getMethodInfo (int globalId){
153153
return null;
154154
}
155155
}
156+
157+
public boolean isRecordAccessor() {
158+
if (ci != null && ci.isRecord()) {
159+
for (RecordComponentInfo component : ci.getRecordComponents()) {
160+
if (getName().equals(component.getName()) && getReturnType().equals(component.getType().getName())) {
161+
return true;
162+
}
163+
}
164+
}
165+
return false;
166+
}
156167

168+
public Object invokeAccessor(Object target) throws Exception {
169+
if (target instanceof Record) {
170+
RecordComponentInfo[] components = ci.getRecordComponents();
171+
for (RecordComponentInfo component : components) {
172+
if (getName().equals(component.getName())) {
173+
return component.getAccessor().invoke(target);
174+
}
175+
}
176+
}
177+
return null;
178+
}
179+
157180
public static MethodInfo create (String name, String signature, int modifiers){
158181
return new MethodInfo( name, signature, modifiers);
159182
}

src/main/gov/nasa/jpf/vm/RecordComponentInfo.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package gov.nasa.jpf.vm;
22

3+
import java.lang.reflect.Method;
4+
35
/**
46
* Class representing a Record component in a Java 17+ record class
57
*/
@@ -10,6 +12,8 @@ public class RecordComponentInfo {
1012
private final String signature;
1113
private final AnnotationInfo[] annotations;
1214
private final TypeAnnotationInfo[] typeAnnotations;
15+
private Class<?> type;
16+
private Method accessor;
1317

1418
public RecordComponentInfo(String name, String descriptor, String signature,
1519
AnnotationInfo[] annotations, TypeAnnotationInfo[] typeAnnotations) {
@@ -39,4 +43,10 @@ public AnnotationInfo[] getAnnotations() {
3943
public TypeAnnotationInfo[] getTypeAnnotations() {
4044
return typeAnnotations;
4145
}
46+
public Class<?> getType() {
47+
return type;
48+
}
49+
public Method getAccessor() {
50+
return accessor;
51+
}
4252
}

src/main/gov/nasa/jpf/vm/StackFrame.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,16 @@ public void reset() {
155155
pc = mi.getInstruction(0);
156156
}
157157

158-
158+
public void setReturnValue(Object value) {
159+
if (value instanceof Integer) {
160+
push((Integer) value, false);
161+
} else if (value instanceof Long) {
162+
push((int) ((Long) value).longValue(), false);
163+
} else {
164+
// Handle non-primitive types differently (e.g., store them as attributes)
165+
setOperandAttr(value);
166+
}
167+
}
159168

160169
protected FixedBitSet createReferenceMap (int nSlots){
161170
if (nSlots <= 64){
@@ -1369,6 +1378,7 @@ public boolean isReflection(){
13691378
return ((attributes & ATTR_IS_REFLECTION) != 0);
13701379
}
13711380

1381+
13721382
// all the dupses don't have any GC side effect (everything is already
13731383
// on the stack), so skip the GC requests associated with push()/pop()
13741384

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package gov.nasa.jpf.test;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
6+
class RecordTest {
7+
8+
record Person(String name, int age) {}
9+
10+
@Test
11+
void testRecordAccessor() {
12+
Person p = new Person("John", 30);
13+
assertEquals("John", p.name());
14+
assertEquals(30, p.age());
15+
}
16+
}

0 commit comments

Comments
 (0)