Skip to content

Commit c8a2448

Browse files
Model Class.forName with unique Class objects
Commit 75beb6b made Class.forName call CProver.notModelled(), as it does not run static initializers. Object.getClass() is implemented as Class.forName(CProver.classIdentifier(this)), so every path through getClass() was cut off: for example, assertions after a call to Object.toString(), Enum.compareTo, or an equals() method that compares getClass() results were reported as SUCCESS without being checked. The model before that commit, which kept Class objects in two static arrays of size 8, had problems as well: - With more than 8 classes, forName returned a nondeterministic object. - Class literals (Foo.class) were not in the arrays, so Class.forName("Foo") == Foo.class was false. - With JBMC's --nondet-static, the non-final static arrays are initialised nondeterministically, which made the search for a class name compare it with nondeterministic strings: JBMC's nondet-static regression test did not terminate within 15 minutes with that model, and finishes within a second when the arrays are final. Keep all Class objects in a linked list instead, whose head is stored in a final array so that --nondet-static leaves it alone. cproverInitializeClassLiteral, which JBMC calls to initialise each class literal, adds the literal to the list, and forName adds a new Class object only if none with that name exists yet. Thus there is one Class object per class name, as in the JVM. forName now throws NullPointerException for a null name, as the JVM does. It still does not run static initializers, and does not throw ClassNotFoundException for names of classes that do not exist; its documentation now says so. Each lookup compares the name with those of all Class objects created so far using String.equals, which is expensive for JBMC's string solver when there are many of them: a program that calls getClass() on objects of 10 different classes took 81 seconds. JBMC therefore implements getClass() for all classes except java.lang.Object itself without going through forName (diffblue/cbmc#9390), so that this lookup is only used for explicit calls of Class.forName and for getClass() on objects of type java.lang.Object. For class literals to be added to the list, JBMC must initialise class literals after the static fields of java.lang.Class, which diffblue/cbmc#9390 also does; with older versions of JBMC, Class.forName("Foo") == Foo.class remains false. This builds on #43, which makes the models' internal uses of forName (getPrimitiveClass, getSuperclass) use a separate factory instead. With these models (and #49 for building with JDK 25), 3 of JBMC's regression tests fail, compared with 113 with the current master branch: two depend on the numbering of dynamic objects in traces, and one fails JBMC's trace validation also without this change. This holds both with and without diffblue/cbmc#9390. With #43 and #49, but without this change, 3 tests fail as well, but two of them because getClass() on an object of type java.lang.Object reaches CProver.notModelled(). Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent b6ac7e5 commit c8a2448

1 file changed

Lines changed: 37 additions & 25 deletions

File tree

‎src/main/java/java/lang/Class.java‎

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,18 @@ private static Class<?> cproverClassWithName(String className) {
120120
private boolean cproverIsMemberClass;
121121
private boolean cproverIsEnum;
122122

123-
// TODO: these fields are to enforce singleton semantics of
124-
// Class objects as returned by Object.getClass() and Class.forName()
125-
// The size is currently hard-coded which may lead to incorrect results.
126-
// A map could be used here in future.
127-
private static String[] cproverClassNames = new String[8];
128-
private static Class<?>[] cproverClassInstances = new Class<?>[8];
123+
// DIFFBLUE MODEL LIBRARY
124+
// The JVM creates exactly one Class object per class, so that, e.g.,
125+
// a.getClass() == b.getClass() holds if and only if a and b have the same
126+
// runtime type, and a.getClass() == A.class if and only if a has type A.
127+
// We keep all Class objects created so far in a linked list: the class
128+
// literals, which JBMC creates and initialises via
129+
// cproverInitializeClassLiteral, and the ones that forName (and thus
130+
// getClass) creates for classes that do not have a class literal.
131+
// The head of the list is stored in a final array so that --nondet-static
132+
// does not replace it with a nondeterministic value.
133+
private static final Class<?>[] cproverClasses = new Class<?>[1];
134+
private Class<?> cproverNextClass;
129135

130136
public String toString() {
131137
return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
@@ -179,29 +185,34 @@ public String toGenericString() {
179185
}
180186
}
181187

182-
// TODO: This is a very partial model of the actual behaviour of the Java
183-
// forName method. The goal is to correctly model combinations of forName
184-
// and getName, but precisely following the JDK behaviour is more involved.
188+
/**
189+
* @diffblue.limitedSupport
190+
* Returns the unique Class object for the given class name, creating it if
191+
* necessary. Unlike the JVM, this does not run the static initializer of
192+
* the class, and does not throw ClassNotFoundException for names of classes
193+
* that do not exist.
194+
*/
185195
public static Class<?> forName(String className) {
186-
CProver.notModelled();
187-
throw new AssertionError();
188-
/*
189-
String foundName = null;
190-
for (int i = 0; i < cproverClassNames.length; ++i) {
191-
String currentName = cproverClassNames[i];
192-
if (currentName == null) {
193-
Class<?> c = new Class();
194-
c.name = className;
195-
cproverClassNames[i] = className;
196-
cproverClassInstances[i] = c;
196+
// DIFFBLUE MODEL LIBRARY
197+
if (className == null) {
198+
throw new NullPointerException();
199+
}
200+
for (Class<?> c = cproverClasses[0]; c != null; c = c.cproverNextClass) {
201+
if (className.equals(c.name)) {
197202
return c;
198203
}
199-
if (className.equals(currentName)) {
200-
return cproverClassInstances[i];
201-
}
202204
}
203-
return CProver.nondetWithoutNullForNotModelled();
204-
*/
205+
Class<?> c = new Class();
206+
c.name = className;
207+
c.cproverRegister();
208+
return c;
209+
}
210+
211+
// DIFFBLUE MODEL LIBRARY
212+
// Add this Class object to the list of all Class objects.
213+
private void cproverRegister() {
214+
cproverNextClass = cproverClasses[0];
215+
cproverClasses[0] = this;
205216
}
206217

207218
public static Class<?> forName(String name, boolean initialize,
@@ -567,6 +578,7 @@ public void cproverInitializeClassLiteral(
567578
this.cproverIsLocalClass = isLocalClass;
568579
this.cproverIsMemberClass = isMemberClass;
569580
this.cproverIsEnum = isEnum;
581+
cproverRegister();
570582
}
571583

572584
/**

0 commit comments

Comments
 (0)