deep

a Cross Development Platform for Java

User Tools

Site Tools


dev:crosscompiler:type_checking

This is an old revision of the document!


Type Checking

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.

check for
ClassA
check for
[S
check for
[ClassA
Object is of type ClassA
1. ref != null, read tag
2. array bit is 0
3. check type (see type checking for regular class)
returns true :-D
1. ref != null, read tag
2. array bit is 0
returns false
1. ref != null, read tag
2. array bit is 0
returns false
Object is of type [S
1. ref != null, read tag
2. array bit is 1
returns false
1. ref != null, read tag lesen
2. array bit is 1
3. tag = type desc. of [S
returns true :-D
1. ref != null, read tag
2. array bit is 1
3. check dimension
4. component type = 0
returns false
Object is of type [ClassA
1. ref != null, read tag
2. array bit is 1
returns false
1. ref != null, read tag
2. array bit is 1
3. tag = type desc. of [S
returns false
1. ref != null, read tag
2. array bit is 1
3. check dimension
4. component type != 0
5. get component type from type desc.
6. check for type
returns true :-D


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 und es wird auf [IA oder auch ein Superinterface von IA geprüft. Im Punkt 6. wird ebenfalls im TD auf den Interfacetyp geprüft. Ist das Objekt vom Typ [IA und wird auf [ClassA geprüft, wird wiederum im Punkt 6. im TD auf den Typ ClassA geprüft. Aus diesem Grund müssen die Descriptoren der Interfaceklassen gleich die wie Typdescriptoren der Standardklassen auch alle Basistypen eingetragen haben. Das ist bei den Interfaces nur Object, alle anderen Einträge müssen leer sein (siehe unten Typprüfung auf normale Klasse).
Hinweis: Sobald ein Typtest auf ein Array von Interfaces gemacht wird, wird für dieses Interface ein Typdescriptor erstellt. Wenn das Objekt zur Laufzeit aber vom Typ Array of Std-Klasse ist, wird der Interface-Typdescriptor gar nicht verwendet. Das ist erst der Fall, wenn der Typ zur Laufzeit auch selber Array of Interfaces ist.

Spezialfall Object

Wenn auf den Typ Object geprüft wird, muss diese Prüfung auch im Falle eines Arrays true liefern.
Wird auf ein Array of Object geprüft, sind zwei Fälle zu unterscheiden

  1. Zeigt die Referenz auf ein Array von normalen Klassen, liefert die Prüfung true, wenn die zu prüfende Dimension kleiner oder gleich der aktuellen Dimension ist.
  2. Zeigt die Referenz auf ein Array von Basistypen, liefert die Prüfung true, wenn die zu prüfende Dimension kleiner als die aktuelle Dimension ist.

checkcast

Die obenstehende Beschreibung galt für instanceof. Für checkcast ändert sich folgendes:

  1. Die Prüfung auf Object muss nie gemacht werden, wird bereits im Bytecode eliminiert.
  2. Bei einer misslungenen Prüfung wird eine Trap-Instruktion ausgelöst.
  3. Eine null-Referenz liefert eine positive Prüfung.

Type Checking for Regular Class

Die Instruktionen checkcast und instanceof holen als Operanden eine Objektreferenz vom Stapel. Der zu prüfende Typ wird im Konstantenpool referenziert. Die Typprüfung läuft folgendermassen ab:

  1. Tag von objectref lesen (beinhaltet Adresse des momentanen Typdescriptors)
  2. Die zu überprüfende Basisklasse muss im Typdescriptor vorkommen. Zu diesem Zweck berechnet der Compiler den Offset im Typdescriptor, der zu dieser Basisklasse gehört.
  3. Über den Tag und den berechneten Offset wird die Adresse der Basisklasse geholt.
  4. Diese Adresse muss mit der Adresse des zu überprüfenden Typs übereinstimmen.

Der Typdescriptor einer Klasse ist wie folgt aufgebaut. Basisklasse 0 ist stets Object. Gespeichert wird dabei die Adresse des Typdescriptors von Object. Dann folgen alle weiteren Oberklassen. Die Adresse der eigenen Klasse wird als letzter Eintrag ebenfalls eingefügt. Alle abgeleiteten Typen müssen die gleiche Reihenfolge der Klassen aufweisen, wie die Basisklassen. Das folgende Beispiel soll den Ablauf veranschaulichen.

Beispiel für Typenprüfung

Für den folgenden Code

ClassA1 o = new ClassA20();
((ClassA20)o).m1();

berechnet der Compiler den Offset 2 im Typdescriptor von ClassA20, weil die Klasse ClassA20 die 2. Erweiterung von Object ist. Die Objektreferenz o zeigt zur Laufzeit auf ClassA20. Daher wird die Adresse von ClassA20 aus dem Typdescriptor geholt und mit ClassA20 verglichen. Die Typenprüfung ist erfolgreich.

ClassA1 o = new ClassA3();
((ClassA20)o).m1();

Hier berechnet der Compiler ebenfalls den Offset 2. Die Objektreferenz o zeigt zur Laufzeit auf ClassA3. Mit dem Offset 2 wird ebenfalls die Adresse von ClassA20 geholt und die Typenprüfung ist erfolgreich.

ClassA1 o = new ClassA21();
((ClassA20)o).m1();

Auch hier berechnet der Compiler den Offset 2. Die Objektreferenz o zeigt jetzt aber auf ClassA21. Mit dem Offset 2 wird die Adresse von ClassA21 geholt und mit ClassA20 verglichen. Die Typenprüfung muss fehlschlagen.
Es muss sichergestellt werden, dass alle Typdecriptoren die gleiche Anzahl von Einträgen aufweisen. Das bedeutet, dass die Descriptoren der Basisklassen mit der notwendigen Anzahl von leeren Einträgen gefüllt werden müssen. Damit wird vermieden, dass ungültige Adressen gelesen werden können. Als Beispiel dazu:

ClassA1 o = new ClassA21();
((ClassA3)o).m1();

Hier wird der Offset 3 berechnet. Im Typdescriptor von ClassA21 darf beim Offset 3 kein zufälliger anderer Eintrag vorhanden sein.
Warum müssen alle Typdescriptoren die gleiche (maximale) Anzahl von Einträgen aufweisen? Nehmen wir den Code

Object o;
((ClassX)o).m1();

Weil o vom Typ statischen Typ Object ist, der dynamische Typ aber beliebig sein kann, kann hier auf einen beliebigen Typ geprüft werden. ClassX könnte z.B. den Offset 10 haben. Also müssen alle Typdescriptoren mindestens 10 (wenn auch leere) Einträge haben.

Type Check for Interface Type

Interfaceklassen dürfen nicht in der Tabelle mit den Standardklassen aufgeführt werden. Der folgende Fall zeigt warum:

Interfaceklassen haben unterschiedlichen Offset

Je nach Vererbungshierarchie belegt das Interface einen anderen Platz in der Tabelle und der Compiler kann keinen fixen Offset berechnen mit dem in der Tabelle geprüft werden könnte.
Aus diesem Grund gibt es eine Tabelle im Typdescriptor mit allen Interfaces, die diese Klasse implementiert und auf die überhaupt je ein Typtest gemacht wird.

Typdescriptor mit CheckIds seiner Interfaces

Die betroffenen Interfaces werden alle nummeriert (chkId) und in der Tabelle beginnend mit der grössten Nummer eingetragen. Die Typprüfung läuft nun so ab, dass der Compiler die chkId der zu testenden Klasse in der Tabelle des Typdescritors des aktuellen Typs sucht. Wird sie gefunden, liefert der Test true andernfalls false. Der letzte Eintrag in der Tabelle muss 0 sein.
Auch hier muss wieder wie oben ein Unterschied zwischen checkcast und instanceof gemacht werden.

Anwendungsfall: Typprüfung

Der folgende Code

  Object[][][] a1 = new Object[2][3][4];
  A[] x = new AA[5];
  a1[0][0] = x;
  a1[0][1][2] = x;
  short[][] y = new short[2][3];
  a1[1][0] = y;
  a1[1][1][0] = y;

führt auf die folgende Abbildung

Speicherabbild für obenstehendes Beispiel


Dieses Beispiel befindet sich auch in den Testfällen (ch.ntb.inf.deep.comp.targettest.arrays.ArrayInstanceTest.testInstance4).

dev/crosscompiler/type_checking.1427627449.txt.gz · Last modified: 2016/02/25 13:33 (external edit)