Skip to content

Commit 9fb347b

Browse files
committed
JACOBIN-886 Added tests for the new java/lang/Class handling
1 parent 9a119f9 commit 9fb347b

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

src/classloader/jlc_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Jacobin VM - A Java virtual machine
3+
* Copyright (c) 2026 by the Jacobin Authors. All rights reserved.
4+
* Licensed under Mozilla Public License 2.0 (MPL 2.0) Consult jacobin.org.
5+
*/
6+
7+
package classloader
8+
9+
import (
10+
"jacobin/src/globals"
11+
"jacobin/src/object"
12+
"jacobin/src/stringPool"
13+
"jacobin/src/trace"
14+
"testing"
15+
)
16+
17+
func TestMakeJlcObject(t *testing.T) {
18+
globals.InitGlobals("test")
19+
trace.Init()
20+
21+
className := "java/lang/String"
22+
jlcObj := MakeJlcObject(className)
23+
24+
if jlcObj == nil {
25+
t.Fatal("MakeJlcObject returned nil")
26+
}
27+
28+
// Verify the KlassName of the returned object is java/lang/Class
29+
// (StringPoolJavaLangClassIndex is used in MakeJlcObject)
30+
classNameStrPtr := stringPool.GetStringPointer(jlcObj.KlassName)
31+
if classNameStrPtr == nil || *classNameStrPtr != "java/lang/Class" {
32+
t.Errorf("Expected KlassName to point to 'java/lang/Class', got '%v'",
33+
*classNameStrPtr)
34+
}
35+
36+
// Verify the "name" field contains a String object with the correct class name
37+
jlcObj.ThMutex.RLock()
38+
nameField, ok := jlcObj.FieldTable["name"]
39+
jlcObj.ThMutex.RUnlock()
40+
41+
if !ok {
42+
t.Fatal("Jlc object missing 'name' field")
43+
}
44+
45+
nameObj, ok := nameField.Fvalue.(*object.Object)
46+
if !ok {
47+
t.Fatalf("Jlc object 'name' field is not an *object.Object, got %T", nameField.Fvalue)
48+
}
49+
50+
actualName := object.GoStringFromStringObject(nameObj)
51+
if actualName != className {
52+
t.Errorf("Expected Jlc name to be '%s', got '%s'", className, actualName)
53+
}
54+
55+
// Verify $statics field is an empty string slice
56+
jlcObj.ThMutex.RLock()
57+
staticsField, ok := jlcObj.FieldTable["$statics"]
58+
jlcObj.ThMutex.RUnlock()
59+
60+
if !ok {
61+
t.Fatal("Jlc object missing '$statics' field")
62+
}
63+
64+
staticsSlice, ok := staticsField.Fvalue.([]string)
65+
if !ok {
66+
t.Fatalf("Jlc object '$statics' field is not a []string, got %T", staticsField.Fvalue)
67+
}
68+
69+
if len(staticsSlice) != 0 {
70+
t.Errorf("Expected empty '$statics' slice, got length %d", len(staticsSlice))
71+
}
72+
}
73+
74+
// Test that the String class is loaded and initialized correctly
75+
// and that the java/lang/Class instance in the loaded class is correct
76+
func TestStringClassStaticsLoaded(t *testing.T) {
77+
// Full initialization required to load base classes and string pool
78+
globals.InitGlobals("test")
79+
trace.Init()
80+
Init() // This calls InitMethodArea, JmodMapInit, GetBaseJmodBytes
81+
82+
// Load the base classes which includes java.lang.String
83+
LoadBaseClasses()
84+
85+
className := "java/lang/String"
86+
87+
// Fetch the class from the Method Area
88+
k := MethAreaFetch(className)
89+
if k == nil {
90+
t.Fatalf("Class %s not found in MethArea after LoadBaseClasses", className)
91+
}
92+
93+
// Retrieve the ClassObject (the JLC instance) from the class data
94+
classObj := k.Data.ClassObject
95+
if classObj == nil {
96+
t.Fatalf("ClassObject for %s is nil", className)
97+
}
98+
99+
// Verify the "name" field contains a String object with the correct class name
100+
classObj.ThMutex.RLock()
101+
nameField, ok := classObj.FieldTable["name"]
102+
classObj.ThMutex.RUnlock()
103+
104+
if !ok {
105+
t.Fatal("Class object missing 'name' field")
106+
}
107+
108+
// Note: in convertToPostableClass, the 'name' field is set as a String object
109+
nameObj, ok := nameField.Fvalue.(*object.Object)
110+
if !ok {
111+
t.Fatalf("Class object 'name' field is not an *object.Object, got %T", nameField.Fvalue)
112+
}
113+
114+
actualName := object.GoStringFromStringObject(nameObj)
115+
if actualName != className {
116+
t.Errorf("Expected Class object name to be '%s', got '%s'", className, actualName)
117+
}
118+
119+
// Verify that the $statics slice contains expected static fields from java.lang.String
120+
// serialVersionUID is universally present in Serializable classes like String.
121+
expectedStatic := "serialVersionUIDJ" // Name + Descriptor
122+
123+
classObj.ThMutex.RLock()
124+
staticsField, ok := classObj.FieldTable["$statics"]
125+
classObj.ThMutex.RUnlock()
126+
127+
if !ok {
128+
t.Fatal("Class object missing '$statics' field")
129+
}
130+
131+
staticsSlice, ok := staticsField.Fvalue.([]string)
132+
if !ok {
133+
t.Fatalf("Class object '$statics' field is not a []string, got %T", staticsField.Fvalue)
134+
}
135+
136+
found := false
137+
for _, s := range staticsSlice {
138+
if s == expectedStatic {
139+
found = true
140+
break
141+
}
142+
}
143+
144+
if !found {
145+
t.Errorf("Expected static field '%s' not found in java.lang.String $statics array", expectedStatic)
146+
}
147+
148+
// Verify the $klass pointer points back to the metadata
149+
classObj.ThMutex.RLock()
150+
klassField, ok := classObj.FieldTable["$klass"]
151+
classObj.ThMutex.RUnlock()
152+
153+
if !ok {
154+
t.Fatal("Class object missing '$klass' field")
155+
}
156+
157+
klassDataPtr, ok := klassField.Fvalue.(*ClData)
158+
if !ok {
159+
t.Fatalf("Class object '$klass' field is not a *ClData, got %T", klassField.Fvalue)
160+
}
161+
162+
if klassDataPtr.Name != className {
163+
t.Errorf("Expected $klass pointer to point to ClData for '%s', got '%s'", className, klassDataPtr.Name)
164+
}
165+
}

0 commit comments

Comments
 (0)