Nietypowe wywołanie invokespecial z Jasmin (Java assembler)

Byłem ciekaw czy z klasy Java można wywołać invokespecial na rzecz nieprywatnej metody przywiązanej do instancji obiektu z zewnątrz jego klasy. Za najprostszą metodę stworzenia takiej “dziwnej” klasy uznałem użycie assemblera Jasmin. Był to dobry pretekst do zapoznania się z tym narzędziem.
Stworzyłem dwie klasy w Javie:
Plik A.java:

package eu.zacheusz.invoketry;
 
public class A {
	public void method () {
		System.out.println("A.method");
	}
}

oraz plik B.java:

package eu.zacheusz.invoketry;
 
public class B extends A {
	@Override
	public void method () {
		System.out.println("B.method");
	}
}

Oraz klasę napisaną w Jasmin. Plik Invoking.j:

.source Invoking.j
.class public eu/zacheusz/invoketry/Invoking
.super java/lang/Object

	.method public static main([Ljava/lang/String;)V
  		new eu/zacheusz/invoketry/B	;new B();
  		dup	;kopiowanie referencji do stworzonego obiektu na stosie
  		invokespecial eu/zacheusz/invoketry/B/()V ;domyślny konstruktor
  		invokespecial  eu/zacheusz/invoketry/A/method()V
  		return
	.end method

Polecenie invokespecial eu/zacheusz/invoketry/A/method()V ma za zadanie wywołać metodę z klasy eu.zacheusz.invoketry.A na rzecz obiektu typu eu.zacheusz.invoketry.B. Normalne wywołanie (invokevirtual) powinno spowodować uruchomienie kodu z przeciążonej metody.
Kompilacja klasy napisanej w assemblerze:
java -jar jasmin.jar -d compiled jassrc\Invoking.j
gdzie jassrc to katalog ze źródłami a compiled katalog przeznaczony na skompilowane klasy (są już w nim eu/zacheusz/invoketry/A.class oraz eu/zacheusz/invoketry/B.class). Wygenerowanie klasy potwierdził komunikat:
Generated: compiled\eu\zacheusz\invoketry\Invoking.class.

Pierwsza próba
java -cp compiled eu.zacheusz.invoketry.Invoking;
zakończla się niepowodzeniem:
Exception in thread "main" java.lang.VerifyError: (class: eu/zacheusz/invoketry/Invoking, method: main signature: ([Ljava/lang/String;)V) Illegal use of nonvirtual function call
Could not find the main class: eu.zacheusz.invoketry.Invoking. Program will exit.

Zadziałał weryfikator. Po wyłączeniu go za pomocą opcji -Xverify:none udało się uruchomić skompilowany kod:
java -Xverify:none -cp compiled eu.zacheusz.invoketry.Invoking
A.method

Mimo, że klasa eu.zacheusz.invoketry.B ma metodę o sygnaturze method()V została wywołana metoda klasy eu.zacheusz.invoketry.A. Po dodaniu kolejnej klasy do hierarchii w pliku C.java:

package eu.zacheusz.invoketry;
 
public class C extends B {
	@Override
	public void method () {
		System.out.println("C.method");
	}
}

oraz modyfikacji klasy napisanej w assemblerze:

.source Invoking.j
.class public eu/zacheusz/invoketry/Invoking
.super java/lang/Object

	.method public static main([Ljava/lang/String;)V
  		new eu/zacheusz/invoketry/B	;new B();
  		dup	;kopiowanie referencji do stworzonego obiektu na stosie
  		invokespecial eu/zacheusz/invoketry/C/()V ;domyślny konstruktor
  		invokespecial  eu/zacheusz/invoketry/A/method()V
  		return
	.end method

Po skompilowaniu (java -jar jasmin.jar -d compiled jassrc\Invoking.j) próba uruchomienia powoduje błąd maszyny wirtualnej:
java -Xverify:none -cp compiled eu.zacheusz.invoketry.Invoking
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00000000, pid=5592, tid=2284
#
# JRE version: 6.0_22-b04
# Java VM: Java HotSpot(TM) Client VM (17.1-b03 mixed mode, sharing windows-x86 )
# Problematic frame:
# C 0x00000000

Okazuje się, że w ten sposób można wywołać jedynie metodę klasy będącej bezpośrednim rodzicem. Jest to możliwe dlatego, że wywołania metod na rzecz super są obsługiwane właśnie przez invokespecial. Rozważając przykładową klasę:

package eu.zacheusz.invoketry;
 
public class X extends A {
	public void m () {
		super.method();
	}
}

Po jej zdeassemblowaniu (javap -c -classpath compiled eu.zacheusz.invoketry.X) kod metody m zawiera identyczne wywołanie:
public void m();
Code:
0: aload_0
1: invokespecial #15; //Method eu/zacheusz/invoketry/A.method:()V
4: return

Podsumowując, udało się napisać kod wywołujący przesłoniętą metodę klasy nadrzędnej spoza kodu klasy, ale uruchomienie go wymagało wyłączenia walidacji.

Zacheusz Siedlecki

Komentarze

Chcesz coś napisać?





*