javaseiten.de   |   Version 0.6
 

5.8. Weitere Direktiven

.debug, .annotation, .deprecated, .signature, .inner, .enclosing method, .stack

 

In den vorangegangenen Abschnitten wurden bereits wichtige Direktiven genannt, um lauffähige Assemblerprogramme erstellen zu können. Darüber hinaus stellt die Assemblersprache Jasmin weitere Direktiven zur Verfügung, die z.B. die Verwendung von Annotationen oder die Aufnahme zusätzlicher Debugging-Informationen ermöglichen.

 

Debugging-Informationen mit .debug in die Klassendatei aufnehmen

Durch die Verwendung der Direktive .debug kann der Assembler dazu veranlasst werden, zusätzliche Debugging-Informationen in die zu erstellende Klassendatei zu schreiben. Die Direktive kann dabei innerhalb des Headers eines Jasmin-Quelltextes verwendet werden.

.debug "<text>"

Für <text> kann eine beliebige Zeichenkette (UTF-8-Codierung) gewählt werden. Die Bytes, die diese Zeichenkette codieren werden durch den Übersetzungsvorgang an das Ende der Klassendatei angehängt. Das folgende kurze Listing soll zunächst durch Jasmin in eine .class-Datei übersetzt werden, in der anschließend die Zeichenkette "Hello Debugger!" lokalisiert werden soll.

Listing 5.11. DebugExample.j. Anfügen von Debugging-Informationen an das Ende der Klassendatei.

; DebugExample.j

.bytecode 50.0
.class public DebugExample
.super java/lang/Object
.debug "Hello Debugger!"

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 0
  .limit locals 1
  return
.end method

Durch ein geeignetes Programm kann aus DebugExample.class ein Hexdump erzeugt werden. Die linke Spalte besteht aus den einzelnen Zahlenwerten, jeweils im Bereich von 0x00 bis 0xff, aus denen die Datei besteht. Die mittler Spalte enthält eine ASCII-Repräsentation dieser Zahlenwerte, falls möglich (UTF-8 entpricht im Bereich von 0x00 bis 0x7f dem ASCII-Code). Die dritte Spalte enthält die Nummerierung des jeweiligen linken Bytes der ersten Spalte, wobei dem ersten Byte die Nummer null zugwiesen wird.

Hexdump von DebugExample.class:

ca fe ba be .. .. .. .. .. .. .. .. .. .. .. ..     ................     0
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     ................     16
...
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     ................     224
.. .. .. .. .. .. .. 00 0c 00 00 00 0f 48 65 6c     .............Hel     240
6c 6f 20 44 65 62 75 67 67 65 72 21                 lo Debugger!         256

Ab dem Byte 253 beginnt die ASCII-Codierung von "Hello Debugger!". Dabei enspricht z.B. der hexadezimale Zahlenwert 48 dem Buchstaben "H". Die Zeichencodierung ist innerhalb eines Byteabschnittes eingebettet, der als SourceDebugExtension-Attribut bezeichnet wird. Die beiden Bytes 00 0c zu Beginn dieses Abschnitts verweisen auf einem Byteabschnitt im Konatantenpool (Utf8-Konstante), der die Bezeichnung des Attributs, also die Zeichenkette "SourceDebugExtension" wiederum als ASCII-Codierung speichert. Die folgenden Bytes 00 00 00 0f stehen für die Anzahl der nachfolgenden Bytes (Codierungslänge von "Hello Debugger!").

 

Annotationen mit .annotation

Die Beschreibung der .annotation-Direktiven baut auf Abschnitt 2 auf, in dem auf die Verwednung von Annotationen in Java-Quelltexten eingegangen wird. Für die Verwendung von Annotationen innerhalb von Jasmin-Quelltexten werden die fünf Direktiven .annotation visible, .annotation invisible, .annotation visibleparam und .annotation invisibleparam zur Verfügung gestellt. Jedem dieser Direktiven kann ein zugehöriger Byteabschnitt innerhalb der Klassendatei zugeordnet weden. Die Definition einer Annotation im Assemblerquelltext mit z.B. .annotation visible wird auf den Byteabschnitt bzw. auf das Attribut RuntimeVisibleAnnotations abgebildet. Nach der Schlüsselbezeichnung .annotation können die Schlüsselwörter visible und invisible folgen. Dabei legen die folgenden Schlüsselwörter fest, ob eine Annotation während der Laufzeit von der virtuellen Java-Maschine "aufrechterhalten" werden, um diese z.B. während der Laufzeit reflektiv auslesen zu können (siehe dazu auch Abschnitt 2.4.7).

.annotation visible <fielddescriptor>
  <elementname> [<arraytag>]<tag> [<fielddescriptor>] [= <elementvalue>]
  ...
.end annotation

.annotation invisible <fielddescriptor>
  <elementname> [<arraytag>]<tag> [<fielddescriptor>] [= <elementvalue>]
  ...
.end annotation

Nach den Schlüsselwörtern visible oder invisible kann der Felddeskriptor angegeben werden, der den Annotationstyps repräsentiert. Die Element-Wert-Paare der Annotation können anschließend in jeweiligen Zeilen angegeben werden. Die Definiton der Annotation wird danach mit .end annotation abgeschlossen. Soll dem Element mit dem Namen value der Annotation @Retention der Elementwert RetentionPolicy.CLASS zugewiesen werden, könnte dies in Jasmin-Syntax wie folgt aussehen:

.annotation visible Ljava/lang/annotation/Retention;
  value e Ljava/lang/annotation/RetentionPolicy; = CLASS
  .end annotation

Der Descriptor Ljava/lang/annotation/Retention; des Annotationstyps Retention macht deutlich, dass sich die Definition des Annotationstyps im Paket java.lang.annotation befindet. Konkret ist darin die Klassendatei Retention.class enthalten. Nach dem Elementnamen value folgt der Typ des Elements durch Angabe eines entsprechenden Zeichens (tag). Im Bespiel wird der tag-Wert "e" verwendet. Er gibt an, dass der Elementtyp ein Aufzählungstyp ist. Der Buchstabe "e" bezieht sich dabei auf das Schlüsselwort enum, mit dessen Hilfe Aufzählungstypen innerhalb von Java-Quelltexten definiert werden können. Nach dem tag-Zeichen schließt sich wiederum der entsprechende Felddeskriptor an, gefolgt vom Zeichen "=" und dem Wert des Elements. Im Beispiel ist der Wert die Enum-Konstante CLASS. Falls für <arraytag> das Zeichen "[" eingesetzt wird, können mehrere Elementwerte angegebenen werden, die durch ein Leerzeichen voneinander getrennt sind. Z.B. könnte die Festlegung des Element-Wert-Paares des Annotationstyps Target wie folgt notiert werden (<elementvalue> besteht aus den drei Enum-Konstanten TYPE, METHOD und PARAMETER, siehe dazu auch Abschnitt 2.4.6):

value [e Ljava/lang/annotation/ElementType; = TYPE METHOD PARAMETER

Neben dem bisher verwendeten tag-Zeichen "e" sind die in der folgenden Tabelle enthaltenen tags zulässig.

Tabelle 5.2. Mögliche tag-Zeichen innerhalb einer Annotationendefinition und deren Bedeutungen.

tag Typ des Elements
B byte
C char
D double
F float
I int
J long
S short
Z boolean
s String
e Aufzählungstyp
c Klassentyp
@ Annotationentyp

Die Zeichen "B", "C", "D", "F", "I", "J", "S" und "Z" stehen jeweils für einen primitiven Datentyp. Hinzukommen "s" und "c", falls ein Element der Annotation vom Typ String oder ein Klassentyp ist. Ein Typ eines Elements einer Annotation kann auch wiederum ein Annotationstyp sein (Annotationen innerhalb von Annotationen). In dem Fall wird als tag-Zeichen "@" gewählt. Das folgende Codefragment zeigt dazu einen Ausschnitt aus Listing 5.14:

value3 @ LAnnotationJasminExample; = .annotation
                                       name s = "Hans Dampf"
                                       id I = 1
                                     .end annotation

Es wird deutlich, dass in diesem Fall für <elementvalue> der Porgrammausschnitt .annotation ... .end annotation steht. Bei eingebetteten Annotationen ist das Schlüsselwort .annotation alleinstehend, d.h. visible oder invisible bzw. ein Felddeskriptor in der Zeile sind nicht zulässig. Die eingebettete Annotation @AnnotationJasminExample wird in Listing 5.12 mit zwei Elementen definiert, die vom Typ String bzw. int sind. Ein Felddeskriptor (Definition der Elemente) innerhalb der eingebetteten Annotation im Beispiel wird nicht benötigt bzw. ist nicht zugelassen, da ein solcher lediglich bei Auszählungstypen und Annotationstypen benötigt wird und vor dem Zeichen "=" platziert werden muss. Ein zu AnnotationJasminExample.j gehörender Java-Quelltext könnte wie folgt formuliert werden:

/* AnnotationJasminExample.java */ 

import java.lang.annotation.*;

@Documented
@Retention(value = RetentionPolicy.CLASS)
@Target(value = { ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER })

public @interface AnnotationJasminExample {
  String name();
  int id() default 1;
}

Das Listing AnnotationJasminExample.java enthält innerhalb der Annotationendefinition das Schlüsselwort default, um einen Default-Wert für das zweite Element vorzugeben. In einem Jasmin-Quelltext kann dazu die Direktive .annotation default genutzt werden, die der anschließenden Syntax unterliegt.

.annotation default
  [<arraytag>]<tag> [<fielddescriptor>] = <elementvalue>
.end annotation

Der Assemblerquelltext AnnotationJasminExample.j verwendet .annotation default zur Vorgabe eines Default-Wertes, wobei die Direktive nur innerhalb einer Methodendefinition, also zwischen .method und .end method, vorkommen kann.

Listing 5.12. AnnotationJasminExample.j. Definition der Annotation @AnnotationJasminExample.

; AnnotationJasminExample.j

.bytecode 50.0
.interface public abstract annotation AnnotationJasminExample
.super java/lang/Object
.implements java/lang/annotation/Annotation

.annotation visible Ljava/lang/annotation/Documented;
  .end annotation
.annotation visible Ljava/lang/annotation/Retention;
  value e Ljava/lang/annotation/RetentionPolicy; = CLASS
  .end annotation
.annotation visible Ljava/lang/annotation/Target;
  value [e Ljava/lang/annotation/ElementType; = TYPE METHOD PARAMETER
  .end annotation

.method public abstract name()Ljava/lang/String;
.end method

.method public abstract id()I
  .annotation default
    I = 1
    .end annotation
.end method

Annotationen können auch bei Methodenparametern genutzt werden. Dazu werden die Direktiven .annotation visibleparam und .annotation invisibleparam zur Verfügung gestellt und können nur innerhalb von Methodendefinitionen stehen. Direkt im Anschluss an die Schlüsselwörter visibleparam und invisibleparam ist die Angabe der Nummer des Paramters erforderlich, für den die Annotation gelten soll. Soll z.B. der erste Paramter einer Methode mit einer Annotation versehen werden, wird für <paramnumber> die Zahl 1 notiert. Die vollständig Syntax einer Parameterannotation lautet wie folgt:

.annotation visibleparam <paramnumber> <fielddescriptor>
  <elementname> [<arraytag>]<tag> [<fielddescriptor>] [= <elementvalue>]
  ...
.end annotation

.annotation invisibleparam <paramnumber> <fielddescriptor>
  <elementname> [<arraytag>]<tag> [<fielddescriptor>] [= <elementvalue>]
  ...
.end annotation

Als Beispiel für die Verwendung einer Paramterannotation soll Listing 5.14 dienen. Im Jasmin-Quelltext wird der Paramter args der main-Methode mit der zuvor durch Listing 5.12 definierten Annotation @AnnotationJasminExample versehen. Das Anwendungsbeispiel AnnotationJasminExample3.j enthält auch eine weitere Annotation, die durch AnnotationJasminExample2.j bzw. dem zugehörigen Java-Quelltext definiert wird.

/* AnnotationJasminExample2.java */ 

import java.lang.annotation.*;

@Target(value = { ElementType.METHOD })
public @interface AnnotationJasminExample2 {
  enum Direction { NORTH, SOUTH, WEST, EAST };
  Direction value1();
  
  Class value2();
  
  AnnotationJasminExample value3();
}

Der folgende Jasmin-Quelltext zur Beschreibung der Annotation @AnnotationJasminExample2 enthält die Direktive .inner, da beim Übersetzten des Java- bzw. Jasmin-Quelltextes die Klassen AnnotationJasminExample2.class und AnnotationJasminExample2$Direction.class erzeugt werden (Aufzählungstype werden in lokale Klassen übersetzt).

Listing 5.13. AnnotationJasminExample2.j. Definition der Annotation @AnnotationJasminExample2.

; AnnotationJasminExample2.j

.bytecode 50.0
.interface public abstract annotation AnnotationJasminExample2
.super java/lang/Object
.implements java/lang/annotation/Annotation
.annotation visible Ljava/lang/annotation/Target;
  value [e Ljava/lang/annotation/ElementType; = METHOD
  .end annotation
; folgende .inner-Direktive in einer einzigen Zeile anordnen
.inner class public static final enum Direction 
    inner AnnotationJasminExample2$Direction outer AnnotationJasminExample2

.method public abstract value1()LAnnotationJasminExample2$Direction;
.end method

.method public abstract value2()Ljava/lang/Class;
.end method

.method public abstract value3()LAnnotationJasminExample;
.end method

Die Beispielanwendung AnnotationJasminExample3.j definiert eine Klasse, die die zuvor definierten Annotationen @AnnotationJasminExample und @AnnotationJasminExample2 verwendet. Die main-Methode enthält zwei Annotationendefinition. Die erste Definition besitzt drei Elemente und ist eine Methodenannotation. Die Elemente haben die Typen: Aufzählungstyp (tag "e"), Klassentyp (tag "c") und Annotationstyp (tag "@"). Das letzte Element erfordert daher die Definition der eingebetteten Annotation. Die zweite Annotationendefinition ist eine Parameterannotation für den einzigen Parameter args der main-Methode.

Listing 5.14. AnnotationJasminExample3.j. Das Beispiel definiert eine Klasse zur Anwendung der zuvor definierten Annotationen @AnnotationJasminExample und @AnnotationJasminExample2.

; AnnotationJasminExample3.j

.bytecode 50.0
.class public AnnotationJasminExample3
.super java/lang/Object
; folgende .inner-Direktive in einer einzigen Zeile anordnen
.inner class public static final enum Direction 
    inner AnnotationJasminExample2$Direction outer AnnotationJasminExample2

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 0
  .limit locals 1
  return
  .annotation invisible LAnnotationJasminExample2;
    value1 e LAnnotationJasminExample2$Direction; = NORTH
    value2 c = Ljava/lang/Integer;
    value3 @ LAnnotationJasminExample; = .annotation
        name s = "Hans Dampf"
        id I = 1
        .end annotation
    .end annotation
  .annotation invisibleparam 1 LAnnotationJasminExample;
    name s = "Hans im Glueck"
    id I = 2
    .end annotation
.end method

Das Jasmin-Listing AnnotationJasminExample3.j ist gleichbedeutend mit dem folgenden Java-Quelltext:

/* AnnotationJasminExample3.java */

public class AnnotationJasminExample3 {

  @AnnotationJasminExample2(
    value1 = AnnotationJasminExample2.Direction.NORTH,
    value2 = Integer.class,
    value3 = @AnnotationJasminExample(name = "Hans Dampf", id = 1)
  )
  public static void main(
    @AnnotationJasminExample(name = "Hans im Glueck", id = 2) String[] args
  ) {
    
  }
}

 

Programmelemente mit .deprecated markieren

Mit der Direktive .deprecated können Klassen, Felder oder Felder als überholt (veraltet) markiert werden. Dadurch wird es z.B. einem Übersetzer ermöglicht einen entsprechenden Hinweis auszugeben, falls in einem Quelltext ein derartiges Programmelement verwendet wird.

.deprecated

Die Direktive .deprecated führt dazu, dass in die zu erstellenden Klassendatei ein Byteabschnitt (Deprecated-Attribut) an entsprechender Stelle aufgenommen wird. Ein Übersetzer kann Klassendateien auf diese Byteabschnitte hin untersuchen, um festzustellen, ob ein Programmelement als "deprecated" eingestuft ist. Ursprünglich wurde das Deprecated-Attribut in JDK 1.1 eingeführt, um das Schlüsselwort @deprecated in Quelltextkommentaren zu unterstützen. Mit der Einführung von Annotationen in die Java-Programmiersprache seht auch die Annotation @Deprecated zur Verfügung (siehe dazu auch Abschnitt 2.4.1). Zum folgenden Java-Quelltext, in dem das Schlüsselwort @deprecated und die Annotation @Deprecated verwendet werden, soll ein Äquivalent auf Assemblerebene gefunden werden.

/* DeprecatedJasminExample.java */

/**
 * @deprecated DeprecatedJasminExample marked deprecated.
 */

public class DeprecatedJasminExample {
  
  @Deprecated private static final String s = "s marked deprecated";
  
  @Deprecated public static void method1 () {
    System.out.println("method1 marked deprecated.");
  }
}

Mit dem Java-Compiler javac kann der Quelltext zunächst eine Klassendatei übersetzt werden. Ein Hexdump von DeprecatedJasminExample.class könnte dann wie folgt aussehen:

Ausschnitt des Hexdumps von DeprecatedJasminExample.class:

ca fe ba be .. .. .. .. .. .. .. .. .. .. .. ..     ................     0
...
00 06 00 00 00 01 00 1a 00 07 00 08 00 03 00 09     ................     464
00 00 00 02 00 0a 
                  00 0b 00 00 00 00 
                                    00 0c 00 00     ................     480
00 06 00 01 00 0d 00 00
                        00 02 00 01 00 0e 00 0f     ................     496
00 01 00 10 00 00 00 1d 00 01 00 01 00 00 00 05     ................     512
2a b7 00 01 b1 00 00 00 01 00 11 00 00 00 06 00     *...............     528
01 00 00 00 07 00 09 00 12 00 0f 00 03 00 10 00     ................     544
00 00 25 00 02 00 00 00 00 00 09 b2 00 02 12 03     ..%.............     560
b6 00 04 b1 00 00 00 01 00 11 00 00 00 0a 00 02     ................     576
00 00 00 0c 00 08 00 0d 
                        00 0b 00 00 00 00 
                                          00 0c     ................     592
00 00 00 06 00 01 00 0d 00 00
                              00 02 00 13 00 00     ................     608
00 02 00 14 
            00 0b 00 00 00 00                       ..........           624

Die Klassendatei enthält insgesamt fünf Byteabschnitte, die mit den Schlüsselwörtern @deprecated und @Deprecated zusammenhängen:

Bytes 486-491 (Deprecated-Attribut):
    00 0b 00 00 00 00
Bytes 492-503 (RuntimeVisibleAnnotations-Attribut):
    00 0c 00 00 00 06 00 01 00 0d 00 00
Bytes 600-605 (Deprecated-Attribut):
    00 0b 00 00 00 00
Bytes 606-617 (RuntimeVisibleAnnotations-Attribut):
    00 0c 00 00 00 06 00 01 00 0d 00 00
Bytes 628-633 (Deprecated-Attribut):
    00 0b 00 00 00 00

Die ersten beiden Bytes der Byteabschnitte codieren einen Index im Konstantenpool der Klassenatei. Den hexadezimalen Indizes 0x000b und 0x000c verweisen auf die 11. und 12. Konstante im Pool. Die beiden Konstanten codieren den Namen der Attribute.

11. CONSTANT_Utf8: Deprecated
12. CONSTANT_Utf8: RuntimeVisibleAnnotations
13. CONSTANT_Utf8: Ljava/lang/Deprecated;

Die RuntimeVisibleAnnotation-Attribute enthalten weiterhin die Bytes 00 0d an entsprechender Position. Die beiden Bytes verweisen auf die 13. Konstante, die den Typ der Annotation durch einen Felddeskriptor codiert. Die aus DeprecatedJasminExample.java entstandene Klassendatei enthält also drei Deprecated-Attribute und zwei RuntimeVisibleAnnotations-Attribute. Das erste Deprecated-Attribut resultiert aus dem Schlüsselwort @deprecated innerhalb des Javadoc-Kommentars. Es wird weiterhin deutlich, dass der Java-Compiler aus der im Quelltext verwendeten Annotation @Deprecated sowohl ein RuntimeVisibleAnnotations-Attribute als auch ein Deprecated-Attribut erzeugt. Dadurch können Tools, die eine Klassendatei z.B. nur nach Deprecated-Attributen analysieren, sowohl die Klassendefinition als auch das markierte Feld und die markierte Methode als "deprecated" erkennen. Ein Äquivalent zum Java-Quelltext DeprecatedJasminExample.java auf Assemblerebene könnte daher wie folgt aufgebaut sein:

Listing 5.15. DeprecatedJasminExample.j.

; DeprecatedJasminExample.j

.bytecode 50.0
.class public DeprecatedJasminExample
.super java/lang/Object
.deprecated

.field private static final s Ljava/lang/String; = "s marked deprecated"
  .deprecated
  .annotation visible Ljava/lang/Deprecated;
    .end annotation
  .end field

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public static method1()V
  .limit stack 2
  .limit locals 0
  getstatic java/lang/System/out Ljava/io/PrintStream;
  ldc "method1 marked deprecated."
  invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
  return
  .deprecated
  .annotation visible Ljava/lang/Deprecated;
    .end annotation
.end method

 

Die Direktive .signature

Signaturen werden verwendet bzw. benötigt, um Typinformationen für z.B. generische Typen oder parametrisierte Typen zu codieren (siehe dazu Abschnitt 3). Die Codierung von Signaturen erfolgt dabei durch Zeichenketten. Die Zeichenkette

<E:Ljava/lang/Object;>Ljava/lang/Object;

ist z.B. eine Klassensignatur zur folgenden Klassendeklaration:

public class SignatureExample<E> { }

Die Typinformationen in Form einer Signatur können z.B. durch die Reflection-API, von Debuggern oder von einem Java-Compiler verwendet werden. Als Ausgangspunkt zur weiteren Beschreibung soll der nachfolgende Java-Quelltext dienen.

/* SignatureExample.java */

public class SignatureExample<E> {
  
  private E value;
    
  public void set(E v) {
    value = v;
  }  
  
  public E get() {
    return value;
  }
}

Wird SignatureExample.java mit Hilfe von javac in eine Klassendatei übersetzt, fügt ein entsprechender Java-Compiler bestimmte Byteabschnitte (Signature-Attribute) in diese Klassendatei ein, um zusätzlich Typinformationen zu speichern. Um diese Byteabschnitte durch den Assembler Jasmin erzeugen zu lassen, kann die Direktive .signature verwendet werden.

.signature "<signature>"

Im Beispiel werden insgesamt vier derartige Byteabschnitte eingefügt (Klassensignatur, Typvariablensignatur und zwei Methodensignaturen). Die Signaturen werden durch eine Grammatik spezifiziert (siehe dazu "The Java Virtual Machine Specification, Third Edition"). Die zum Beispiel benötigten vier Signaturen lauten:

Klassensignatur: <E:Ljava/lang/Object;>Ljava/lang/Object;
    Name des (formalen) Typparameters: E
    Obere Schranke des Typparameters (Bound): Ljava/lang/Object;
      (angegeben als Klassentypsignatur)
    Superklassensignatur: Ljava/lang/Object;
      (Klassentypsignatur)

Typvariablensignatur: TE;

Methodensignaturen: 
    (TE;)V 
    ()TE;

Die Klassensignatur entählt in spitzen Klammern den Namen des Typparameters und dessen obere Schranke. Im Beispiel wird als obere Schranke und als Superklassensignatur Ljava/lang/Object; (Klassentypsignatur) angegeben. Da für den Typparameter E keine Schranke (Bound) angegeben wird, führt dies automatisch zu Ljava/lang/Object;. Die Superklasse von SignatureExample ist definitionsgemäß Object. Die anschließende Klassendeklaration enthält zum Vergleich eine angegebenen Schranke für E und erweitert eine geeignete Klasse Test. Die Schranke und die Superklasse Test wird durch die zugehörige Signatur entsprechend codiert.

public class SignatureExample<E extends Number> extends Test { }
<E:Ljava/lang/Number;>LTest;

Für das Feld value ist die Signatur TE;. Die darin enthaltenen Buchstaben "T" und ";" sind in den zugehörigen Ableitungsregeln der Grammatik fest vorgegeben und "E" ist der im Beispielprogramm gewählte Name des Typparameters. Die Signatur des Feldes, ist auch als Teilbereich innerhalb der beiden Methodensignaturen zu finden. Das anschließende Jasmin-Listing verwendet die Direktive .signature, um die benötigten vier Signature-Attribute in die zu erzeugende Klassendatei zu schreiben.

Listing 5.16. SignatureExample.j.

; SignatureExample.j

.bytecode 50.0
.class public SignatureExample
.super java/lang/Object
.signature "<E:Ljava/lang/Object;>Ljava/lang/Object;"

.field private value Ljava/lang/Object;
  .signature "TE;"
  .end field

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public set(Ljava/lang/Object;)V
  .limit stack 2
  .limit locals 2
  aload_0
  aload_1
  putfield SignatureExample/value Ljava/lang/Object;
  return
  .signature "(TE;)V"
.end method

.method public get()Ljava/lang/Object;
  .limit stack 1
  .limit locals 1
  aload_0
  getfield SignatureExample/value Ljava/lang/Object;
  areturn
  .signature "()TE;"
.end method

 

Realisieren von inneren Klassen mit den Direktive .inner und .enclosing method

Eine Klasse kann innerhalb einer anderen Klasse definiert werden (innere Klasse). Im folgenden Java-Quelltext wird die Klasse Inner innerhalb der umschließenden Klasse InnerExample definiert.

/* InnerExample.java */

public class InnerExample {

  int a;
  
  public class Inner {
    int b;
  }
}

Die Übersetzung mit javac InnerExample.java führt dazu, dass zwei Klassendateien aus dem Quelltext erzeugt werden: InnerExample.class und InnerExample$Inner.class. Der Compiler generiert also für die innere Klasse eine eigene Klassendatei, wobei sich der Name der Datei aus dem Namen der äußeren Klasse und dem Namen der inneren Klasse zusammensetzt (getrennt durch ein Dollarzeichen). Innnerhalb eines Jasmin-Quelltextes wird zur Behandlung von inneren Klassen (Interfaces) die Direktive .inner bereitgestellt.

.inner class <modifiers> [<name>] inner <innername> [outer <outername>]
.inner interface <modifiers> [<name>] inner <innername> [outer <outername>]

Um die beiden im Beispiel erzeugten Klassendatein auf Assemblerebene erzeugen zu können, ist für jede Klassendatei ein eigener Jasmin-Quelltext notwendig. Die umschließende Klasse InnerExample wird durch das nächste Listing definiert.

Listing 5.17. InnerExample.j.

; InnerExample.j

.bytecode 50.0
.class public InnerExample
.super java/lang/Object
.inner class public Inner inner InnerExample$Inner outer InnerExample

.field a I

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

Der Quelltext einhält eine .inner-Direktive, in der nach dem Modifikator der Name Inner der inneren Klasse angegeben ist (vergleiche dazu den Java-Quelltext InnerExample.java). Nach dem Schlüsselwort inner ist die Namensbezeichnung InnerExample$Inner notiert (der Name der äußeren Klasse und das Trennzeichen "$" wird dem Namen Inner vorangestellt). Es schließt sich das Schlüsselwort outer und der Name der äußeren Klasse an. Die Definition der inneren Klasse InnerExample$Inner geschieht jedoch nicht in InnerExample.j, sondern durch das weitere Jasmin-Listing im Anschluss. Der Quelltext enthält ein zweites Feld this$0 mit dem Modifikator synthetic und dem zugehörigen Deskriptor LInnerExample;. Das zusätzliche Feld wird zur Unterstützung von inneren Klassen benötigt.

Listing 5.18. InnerExample$Inner.j.

; InnerExample$Inner.j

.bytecode 50.0
.class public InnerExample$Inner
.super java/lang/Object
.inner class public Inner inner InnerExample$Inner outer InnerExample

.field b I
.field final synthetic this$0 LInnerExample;

.method public <init>(LInnerExample;)V
  .limit stack 2
  .limit locals 2
  aload_0
  aload_1
  putfield InnerExample$Inner/this$0 LInnerExample;
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

Mit InnerExample.java wurde ein Beispiel für eine innere Klasse vorgestellt. Innere Klassen können auch innerhalb von Methoden definiert werden. Es ist auch möglich, Klassen innerhalb von Methoden als anonym zu definieren, wie der folgende Java-Quelltext zeigt.

/* InnerExample2.java */

public class InnerExample2 {

  int a;
      
  public void method1() {
    new InnerExample() {
      int b;
    };
  }
}

Innerhalb der Methode method1 wird eine anonyme Klasse definiert und instanziert. Nach der Angabe new InnerExample folgt die Definition der Klasse. Da es sich bei InnerExample um eine Klasse handelt wird die anonyme Klasse von dieser abgeleitet (die Superklasse der anonymen Klasse ist InnerExample). Wird nach dem Operator new der Name eines Interfaces angegeben, dann implemetiert die anonyme Klasse das angegebene Interface. Das nachfolgenden Jasmin-Listings InnerExample2.j und InnerExample2$1.j sind eine Umsetzung von InnerExample2.java auf Assemblerebene. Der zweite Assemblerquelltext ist für die Beschreibung der anonymen Klasse erforderlich. Der Java-Compiler erzeugt aus InnerExample2.java die beiden Klassendateien InnerExample2.class und InnerExample2$1.class. Der Name der zweiten Klassendatei wird vom Java-Compiler für die anonyme Klasse gewählt. Er besteht aus dem Trennzeichen "$" und einer Nummernangabe. Der Name des zweiten Jasmin-Listings wurd deshalb entsprechend gewählt. Der folgende Quelltext enthält die Direktive .inner, wobei nach dem Modifikator final direkt das Schlüsselwort inner und die Namensbeszeichnung der anonymen Klasse folgt.

Listing 5.19. InnerExample2.j.

; InnerExample2.j

.bytecode 50.0
.class public InnerExample2
.super java/lang/Object
.inner class final inner InnerExample2$1

.field a I

.method public <init>()V
  .limit stack 1
  .limit locals 1
  aload_0
  invokespecial java/lang/Object/<init>()V
  return
.end method

.method public method1()V
  .limit stack 3
  .limit locals 1
  new InnerExample2$1
  dup
  aload_0
  invokespecial InnerExample2$1/<init>(LInnerExample2;)V
  pop
  return
.end method

Der Assemblerquelltext InnerExample2$1.j zur Beschreibung der anonymen Klasse enthält neben der .inner-Direktive die Dirketive .enclosing method.

.enclosing method <methodname><methoddescriptor>

Für <methodname> wird im Beispiel InnerExample2/method1 gewählt. Die Zeichenkette beginnt mit dem Namen der Klasse, in der die Methode definiert ist. Anschließend folgt ein Schrägstrich und der Name der Methode. Für den Namen der Methode wird method1 gewählt, da diese Methode die anonyme Klasse umschließt (enclosing method).

Listing 5.20. InnerExample2$1.j. Jasmin-Quelltext zur Erzeugung der Klassendatei InnerExample2$1.class, die für die Beschreibung der anonymen Klasse benötigt wird.

; InnerExample2$1.j

.bytecode 50.0
.class final InnerExample2$1
.super InnerExample
.enclosing method InnerExample2/method1()V
.inner class final inner InnerExample2$1

.field b I
.field final synthetic this$0 LInnerExample2;

.method <init>(LInnerExample2;)V
  .limit stack 2
  .limit locals 2
  aload_0
  aload_1
  putfield InnerExample2$1/this$0 LInnerExample2;
  aload_0
  invokespecial InnerExample/<init>()V
  return
.end method

 

Realisierung einer StackMapTable mit der Direktive .stack

Mit der Direktive .stack kann ein Byteabschnitt in der zu erzeugenden Klassendatei generiert werden (Version der Klassendatei >= 50.0). Dieser Byteabschnitt wird als StackMapTable-Attribut bezeichnet und ist nur innerhalb eines Code-Attributs zulässig. Das Attribut StackMapTable wird bei der Überprüfung einer Klassendatei auf Gültigkeit verwendet (type checking verifier) und kann aus mehreren sogenannten Stack-Map-Frames bestehen. Ein Stack-Map-Frame spezifiziert den Verifikationstyp der Einträge des Operandenstapels und der Einträge im Array der lokalen Variablen zu Beginn bestimmter Programmabschnitte einer Methode. Weiterhin enthält ein Stack-Map-Frame Angaben zu einem Bytecode-Offset (Offset im code-Array), der die zum Frame zugeordnete Programmstelle festlegt. Die .stack-Direktive ist innerhalb von .method und .end method mit der folgenden Syntax zulässig:

.stack
  [offset {<offset> | <label>}]
  [locals <verificationtype> [<verificationarg>]]
  ...
  [stack  <verificationtype> [<verificationarg>]]
  ...
.end stack

.stack use <integer> locals
  [offset {<offset> | <label>}]
  [stack  <verificationtype> [<verificationarg>]]
  ...
.end stack

Nach dem Schlüsselwort offset kann die Bytecodestelle (Index im Code-Array) durch einen absoluten Offset oder einem Label festgelegt werden. Die Verifikationstypen von lokalen Variablen und Operandenstapelelementen können nach den Schlüsselwörtern locals und stack angegeben werden, wobei für <verificationtype> die folgenden Bezeichner einsetzbar sind:

Top
Integer
Float
Long
Double
Null
UninitializedThis
Object
Uninitialized

Als Beispiel für die Definition eines Stack-Map-Frames soll der folgende Ausschnitt aus Listing 5.21 dienen, der sich auf die Bytecodestelle 30 (Index im Code-Array) der zugehörigen Methode bezieht.

.stack 
  offset 30
  locals Object [Ljava/lang/String;
  locals Object StackExample
  locals Object java/lang/Thread
  stack Object java/lang/InterruptedException
.end stack

Die Direktive .stack macht Angaben zu drei lokalen Variablen und einem Element auf dem Operandenstapel. Für <verificationtype> steht in allen Fällen Object - alle drei lokalen Variablen und das Operandenstapelelement speichern ein Objekt. Das Objekt wird durch eine weitere Angabe nach dem Bezeichner Object näher spezifiziert (ein Objekt ist eine Instanz einer Klasse oder ein Array). Nach den Bezeichnern Object und Uninitialized muss jeweils ein Argument <verificationarg> angegeben werden. Eine .stack-Direktive erzeugt einen einzigen Frame innerhalb eines StackMapTable-Attributs. Die innerhalb einer Methodendefinition durch mehrere .stack-Direktiven definierten Frames werden durch den Übersetzer auf ein einziges StackMapTable-Attribut der Methode abgebildet. Es gibt verschiedenartige Typen von Frames: full_frame, same_frame, same_frame_extended, same_locals_1_stack_item_frame, same_locals_1_stack_item_frame_extended, append_frame und chop_frame (siehe dazu JVM-Specification, Third Edition). Dabei beziehen sich alle Frametypen auf den vorherigen Frame. So gilt z.B. für den Frametyp same_frame: Er besitzt die gleichen locals wie der Vorgängerframe und es sind keine stack-Einträge vorhanden. Der allererste Frame (initaler Frame) wird automatisch zur Verfügung gestellt, indem er unter Beachtung des entprechenden Methodendeskriptors berechnet wird. Als Beispiel für Frames von Typ full_frame und same_frame soll der folgnede Java-Quelltext dienen. Wird dieser mit Hilfe des im JDK 6 enthaltenen Compilers javac übersetzt, enthält die zugehörige Klassendatei ein StackMapTable-Attribut mit den beiden genannten Frametypen.

/* StackExample.java */

public class StackExample implements Runnable {
 
  public void run() {
    System.out.println("run...");
  }  
  
  public static void main(String[] args) {
    StackExample se = new StackExample(); 
    
    Thread t = new Thread(se);
    t.start();
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      System.out.println(e.toString());
    }
  }
}

Im Ausschnitt aus dem Hexdump von StackExample.class beginnt der gesucht Byteabschnitt (StackMapTable-Attribut) bei Byte 807 und endet mit Byte 834.

ca fe ba be 00 00 00 32 00 3b 0a 00 0f 00 1f 09     .......2.;......     0
00 20 00 21 08 00 22 0a 00 23 00 24 07 00 25 0a     . .!.."..#.$..%.     16
...
53 74 72 69 6e 67 3b 00 21 00 05 00 0f 00 01 00     String;.!.......     576
10 00 00 00 03 00 01 00 11 00 12 00 01 00 13 00     ................     592
00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1     ...........*....     608
00 00 00 01 00 14 00 00 00 06 00 01 00 00 00 03     ................     624
00 01 00 15 00 12 00 01 00 13 00 00 00 25 00 02     .............%..     640
00 01 00 00 00 09 b2 00 02 12 03 b6 00 04 b1 00     ................     656
00 00 01 00 14 00 00 00 0a 00 02 00 00 00 06 00     ................     672
08 00 07 00 09 00 16 00 17 00 01 00 13 00 00 00     ................     688
82 00 03 00 04 00 00 00 2a bb 00 05 59 b7 00 06     ........*...Y...     704
4c bb 00 07 59 2b b7 00 08 4d 2c b6 00 09 14 00     L...Y+...M,.....     720
0a b8 00 0c a7 00 0e 4e b2 00 02 2d b6 00 0e b6     .......N...-....     736
00 04 b1 00 01 00 15 00 1b 00 1e 00 0d 00 02 00     ................     752
14 00 00 00 22 00 08 00 00 00 0a 00 08 00 0c 00     ...."...........     768
11 00 0d 00 15 00 0f 00 1b 00 12 00 1e 00 10 00     ................     784
1f 00 11 00 29 00 13 
                     00 18 00 00 00 16 00 02 ff     ....)...........     800
00 1e 00 03 07 00 19 07 00 1a 07 00 1b 00 01 07     ................     816
00 1c 0a 
         00 01 00 1d 00 00 00 02 00 1e              .............        832

Das StackMapTable-Attribut beginnt mit den beiden Bytes 00 18, die auf eine Utf8-Konstante im Konstantenpool verweisen. Die Konstante speichert die Codierung der Zeichenkette "StackMapTable". Die anschließenden vier Bytes legen die Anzahl der nachfolgenden Bytes fest, die diesem Attribut noch angehören (0x00000016 = 22, also es folgen noch 22 Bytes). Die Bytes 813 und 814 geben an, dass sich das Attribut aus insgesamt zwei Frames zusammensetzt.

1. Frame (Typ: full_frame): 
   ff 00 1e 00 03 07 00 19 07 00 1a 07 00 1b 00 01 07 00 1c
2. Frame (Typ: same_frame): 
   0a

Der zweite Frame besteht lediglich aus dem einzigen Byte 0a. Ein Frame vom Typ full_frame beginnt immer mit 0xff bzw. 255. Die anschließenden beiden Bytes sind ein Wert zur Berechnung der Zahlenangabe hinter dem Schlüsselwort offset innerhalb von .stack und .end stack. Danach folgt mit 00 03 die Anzahl der locals-Angaben, wobei jede dieser Angaben mit 07 beginnt und zwei Folgebytes hat, die wiederum eine Konstante im Konstantenpool der Klassendatei adressiert. Das folgende Jasmin-Listing ist eine Assemblerrepräsentation von StackExample.java. Es enthält zwei .stack-Direktiven, die zusammen ein entsprechendes StackMapTable-Attribut in der zugehörigen Klassendatei erzeugen.

Listing 5.21. StackExample.j. Die beiden enthaltenen .stack-Direktiven führen zu einem StackMapTable-Attribut in der zugehörigen Klassendatei.

; StackExample.j

.bytecode 50.0
.class public StackExample
.super java/lang/Object
.implements java/lang/Runnable

.method public <init>()V
  .limit stack 1
  .limit locals 1
  0: aload_0
  1: invokespecial java/lang/Object/<init>()V
  4: return
.end method

.method public run()V
  .limit stack 2
  .limit locals 1
  0: getstatic java/lang/System/out Ljava/io/PrintStream;
  3: ldc "run..."
  5: invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
  8: return
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 3
  .limit locals 4
  0: new StackExample
  3: dup
  4: invokespecial StackExample/<init>()V
  7: astore_1
  8: new java/lang/Thread
  11: dup
  12: aload_1
  13: invokespecial java/lang/Thread/<init>(Ljava/lang/Runnable;)V
  16: astore_2
  17: aload_2
  18: invokevirtual java/lang/Thread/start()V
Label21:
  21: ldc2_w 5000
  24: invokestatic java/lang/Thread/sleep(J)V
Label27:
  27: goto Label41
Label30:
  30: astore_3
  31: getstatic java/lang/System/out Ljava/io/PrintStream;
  34: aload_3
  35: invokevirtual java/lang/InterruptedException/toString()Ljava/lang/String;
  38: invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
Label41:
  41: return
  .catch java/lang/InterruptedException from Label21 to Label27 using Label30
  ; full_frame (frameNumber = 0)
  .stack 
    offset 30
    locals Object [Ljava/lang/String;
    locals Object StackExample
    locals Object java/lang/Thread
    stack Object java/lang/InterruptedException
    .end stack
  ; same_frame (frameNumber = 1)
  .stack 
    offset 41
    locals Object [Ljava/lang/String;
    locals Object StackExample
    locals Object java/lang/Thread
    .end stack
.end method

Zur Definition des zweiten Frames vom Typ same_frame könnte auch die kürzere zweite Syntaxform verwendet werden, da ein derartiger Frame dieselben locals-Einträge wie der vorhergende besitzt.

.stack use 3 locals
  offset 41
.end stack

 

 

 

Diese Seite nutzt Google-Dienste - siehe dazu Datenschutz.

Copyright © 2006, 2007 Harald Roeder