For Bytecode instructions checkcast and instanceof the type must be checked during runtime. Two cases must be considered: checking for an array class or checking for a regular class. Checking for interfaces is discussed below.
Since the dynamic type of an object cannot be known the type checking for any type must work properly. The following table shows all possible casses. ClassA is a regular class, [S is an array of base type (here array of short) and [ClassA is an array of ClassA.
If the object is of type [ClassA and is checked for type [IA (while ClassA implements interface IA), the check must return true. This is ok because under point 6. of the last case the type check will get the type descriptor of ClassA which contains IA. Further details can be found further down in Type Check for Interface Type.
The opposite case must work as well. An object is of type [IA and is checked against [IA or one of its superinterfaces. In 6. there is a check against the interface type. If the object is of type [IA and is checked against [ClassA then this check will be in 6. For this reason, the type descriptors of the interfaces must contain the base classes, similar to the type descriptors of the standard classes. With interfaces this is simply the class Object. All other entries must be empty (see below Type Checking for Regular Class).
Hint: As soon as there is a type check against an array of interfaces, a type descriptor for this interface must be created. On the other hand, if the object at runtime is of type array of standard class, then the interface descriptor won't be used. It will be used solely in case when the type at runtime is itself of type array of interfaces.
When you check against Object, the result must be true
if the object is an array (of any type).
A check against an array of Object leads to two cases:
true
if the dimension (of the type to check against) is lower or equal than the actual dimension.true
if the dimension (of the type to check against) is lower than the actual dimension.All the explanations above were for instanceof. For checkcast there are some modifications:
null
reference results in a positive check.Both instructions checkcast and instanceof fetch an object reference from the operand stack. The type to check against is referenced in the constant pool. The check runs as follows:
The type descriptor of a class is constructed as follows: the base class 0 is always Object. The entry contains the address of the type descriptor of Object. This entry is followed by the other superclasses. The address of the own class is entered as the last entry. All derived classes must have the same order for the base classes. The example below shows the execution of a type check.
For the java code
ClassA1 o = new ClassA20(); ((ClassA20)o).m1();
the compiler calculates the offset in the type descriptor of the class ClassA20 to be 2, because ClassA20 is the second extension of Object. The reference o points to an instance of ClassA20 at runtime. Accordingly, the address of ClassA20 will be retrieved from the type descriptor and compared to ClassA20. The type check will return true
.
ClassA1 o = new ClassA3(); ((ClassA20)o).m1();
In this case the compiler will determine the offset to be 2. The reference points to ClassA3 at runtime. With offset 2 the address of ClassA20 will be fetched and the check is successful.
ClassA1 o = new ClassA21(); ((ClassA20)o).m1();
Here again, the compiler calculates the offset to be 2. However, the reference points to ClassA21. With offset 2 the address of ClassA21 will be fetched and compared to ClassA20. The check must return false
.
All the type descriptors must have the same number of entries. All descriptors must be filled with empty entries up to the maximal number of entries. This ensures that non-valid addresses are read, see the following example.
ClassA1 o = new ClassA21(); ((ClassA3)o).m1();
The offset will be calculated to be 3. In the type descriptor of ClassA21 the entry at offset 3 must be 0.
Let's consider another case:
Object o; ((ClassX)o).m1();
The static type of o is Object. The runtime type could be anything. Therefore, we could check against any type. For instance, ClassX could have the offset 10. Hence, all type descriptors must have at least 10 entries.
Interface classes cannot be listed in the table with the standard classes. The reason is that interface classes do not have fixed offsets.
Depending on the extension hierarchy the interface occupies different places in the table and the compiler cannot calculate a fixed offset to check against.
For this reason the type descriptor has a table with all interfaces that this class implements. The compiler will determine at compile time for which interfaces there is ever a type check. Only these will be inserted into the table.
All the affected interfaces get numbered (chkId) and will be inserted into the table starting with the highest number. The checking can now run as follows: the compiler knows the chkId of the interface to check against. It will search chkId in the table. If found, the check returns true
else false
. The last entry must be 0 in order to end the search.
Here again, checkcast and instanceof differ slightly.