Files
2024-07-20 01:08:57 +02:00

451 lines
29 KiB
TeX

%!TEX root = ../../main.tex
\chapter{Aufgetretene Probleme}
Beim Versuch, im Zuge dieser Studienarbeit Teile des \JTXC{} in \ac{Java-TX} zu konvertieren, sind viele Bugs und fehlende Features aufgefallen. Einige interessante Probleme werden in diesem Kapitel genauer beschrieben. In \autoref{fig:problems_plot} ist eine Gesamtübersicht über die Anzahl der gefundenen Probleme zu sehen. Die Grafik ist in zwei Kategorien unterteilt: Bugs und Feature Anfragen. Bugs beschreiben hierbei Fehler in bereits implementierten Funktionen, während Feature Anfragen gänzlich neue Funktionen beschreiben, meistens handelt es sich hierbei um Standard Java Funktionen, die bislang noch nicht implementiert wurden und keine neuen Funktionen im Bezug auf \ac{Java-TX} Sprachfeatures. Weiter sind die Kategorien nach offenen und geschlossenen Problemen unterteilt.
\begin{figure}[H]
\begin{tikzpicture}
\begin{axis}[
ybar,
enlargelimits=0.15,
legend style={at={(0.5,-0.1)},
anchor=north,
legend columns=-1},
ylabel={Anzahl},
symbolic x
coords={offen,geschlossen},
xtick=data,
nodes near coords,
nodes near coords align={vertical},
width=\textwidth,
height=10cm,
bar width=2cm,
enlarge x limits=0.25
]
\addplot[fill=red!50] coordinates {(offen,8) (geschlossen,27) };
\addplot[fill=blue!50] coordinates {(offen,1) (geschlossen,20) };
\legend{Bugs,Feature Anfragen}
\end{axis}
\end{tikzpicture}
\caption{Gefundene Probleme und neue Funktionen}
\label{fig:problems_plot}
\end{figure}
\section{Neue Funktionen}
Um den \JTXCinJTX{} umzuschreiben sind einige Funktionen notwendig, die zu Beginn der Studienarbeit noch nicht implementiert waren. Über den Zeitraum der Studienarbeit wurden insgesamt 20 neue Funktionen implementiert. Da die For-Each Schleife in \ac{Java-TX} minimal von der Java Syntax abweichen darf, wird sie im folgenden etwas ausführlicher beschrieben. Einige der anderen Funktionen werden nur kurz aufgezählt.
\subsection{For\-Each Schleife}
Die For-Each Schleife, ermöglicht es mit einer eleganten Syntax über eine Variable vom Typ \lstinline|java.lang.Iterable| zu iterieren \cite{noauthor_for-each_nodate}. Ein Beispiel zur Verwendung der For-Each Schleife in Java ist in \autoref{lst:for_each_java} zu sehen.
\begin{lstlisting}[language=java, caption={For-Each Schleife in Java}, label={lst:for_each_java}]
List<String> list = new ArrayList<>();
//entweder
for (String s : list) {
System.out.println(s);
}
// ... oder
for(var s : list) {
System.out.println(s);
}
\end{lstlisting}
Dabei kann der Typ der Variable entweder explizit angegeben, oder durch Angabe des Schlüsselwort \texttt{var} durch den Compiler inferiert werden. In Java-TX kann diese Typangabe Angabe auch gänzlich weggelassen werden.
Ein Beispiel zur Verwendung der For-Each Schleife in \ac{Java-TX} ist in \autoref{lst:for_each_javatx} zu sehen.
\begin{lstlisting}[language=java, caption={For-Each Schleife in Java-TX}, label={lst:for_each_javatx}]
List<String> list = new ArrayList<>();
//Kein Typ vor der Variable s notwendig
for (s : list) {
System.out.println(s);
}
\end{lstlisting}
\subsection{Weitere neue Funktionen}
Neben der For-Each Schleife wurden noch weitere Funktionen implementiert. Diese unterscheiden sich allerdings nicht von der Java Syntax und werden daher nur kurz aufgelistet:
\begin{itemize}
\item super() Konstruktoraufruf
\item Methodenaufruf der Superklasse mittels super
\item this() Konstruktoraufruf
\item instanceof Schlüsselwort
\item throw Schlüsselwort
\item Verwendung des \lstinline|null| Literals
\item \lstinline|Character| Literal z.B. \lstinline|'a'|
\item \lstinline|Long| und \lstinline|Float| Literals z.B. \lstinline|1L| oder \lstinline|1.0f|
\item Type Casts
\item Negation Operator \lstinline|!|
\item Bitweise AND \lstinline|&| und OR \lstinline{|} Operatoren
\item Modulo Operator \lstinline|%|
\item Ternary Operator \lstinline|? :|
\item Do-While Schleife
\item Verwenden von JAR-Archiven im Classpath
\end{itemize}
\section{Bugs}
Insgesamt wurden 27 Bugs gefunden und behoben, einige weitere sind noch offen. Im folgenden Abschnitt wird eine kleine Auswahl dieser Bugs ausführlicher beschrieben.
\subsection{JVM Classpath wird von \JTXC{} beachtet}
Zur Beschreibung dieses Bugs müssen zunächst einige Grundlagen zum Classpath und dem Classloader geklärt werden. Damit der \JTXC{} andere Klassen verwenden kann (darunter zählen z.B. auch JAR-Archive), müssen sie im Classpath vorhanden oder Teil der Standardbibliothek sein. Der Classpath kann, wie auch beim \gls{javac} Compiler, mit dem Flag \lstinline|-cp| oder \lstinline|-classpath| angegeben werden. Ein Beispielaufruf könnte folgendermaßen aussehen: \lstinline|java -jar JavaTXCompiler.jar -cp /path/to/classes /path/to/source.jav|\footnote{JavaTXCompiler.jar ist in diesem Fall der \JTXC{} in Form eines JAR-Archivs}. Wenn kein Classpath angegeben wird, wird das aktuelle Arbeitsverzeichnis des Nutzers verwendet. Die angegebenen Pfade werden dann vom Compiler durchsucht, um die benötigten Klassen zu finden. Wenn die Klasse nicht gefunden werden konnte, wird ein Fehler ausgegeben. Nur Klassen, die im Classpath vorhanden sind, können importiert und verwendet werden. Ausnahme sind die Klassen aus der Java Standard Library, wie z.B. alle Klassen des Pakets \lstinline|java.lang|, die standardmäßig vorhanden sind.
Das Packet \lstinline|java.lang| bietet die Klasse \lstinline|ClassLoader|, die es ermöglicht, Klassen dynamisch in die \ac{JVM} zu laden \cite{baeldung_class_2024}.
Der \JTXC{} verwendet auch diesen Classloader, um die angegebenen Klassen zu suchen und einzulesen. So muss kein eigener Parser für Java Bytecode implementiert werden.
Um genau zu sein gibt es nicht einen Classloader, sondern mehrere, die nacheinander ausgeführt werden. Die oberste Schicht ist der Bootstrap Classloader, der \ac{JDK} interne Klassen lädt. Darunter befindet sich der Extension Classloader, der die Klassen aus den JAR-Dateien im \lstinline|jre/lib/ext| Verzeichnis lädt. Der Application Classloader lädt die Klassen aus dem Classpath und zuletzt gibt es eine eigene Implementierung des \lstinline|java.net.URLClassLoader| namens Directory Classloader, der die Pfade, die dem Compiler mit \lstinline|-cp| übergeben werden, durchsucht \cite{baeldung_class_2024, nero_all_2023}. Eine Visualisierung dieser Schichten ist in \autoref{fig:classloader} zu sehen. Das Programm beginnt mit dem untersten Classloader (Directory Classloader), versucht also die gesuchte Klasse in diesen Ressourcen zu finden. Wenn die Klasse nicht gefunden wird, wird der nächste Classloader (Application Classloader) verwendet. Dieser Prozess wird so lange wiederholt, bis die Klasse gefunden wird oder alle Classloader durchsucht wurden. Zu beachten ist, dass der Classpath des Application Classloaders nicht derselbe ist, wie der Classpath, der dem Compiler übergeben wird und vom DirectoryClassloader verwendet wird. Der Classpath des Application Classloaders ist der Classpath, mit dem die \ac{JVM} gestartet wurde. Dieser beinhaltet in diesem Fall z.B. sämtliche \gls{Maven} Abhängigkeiten und Bytecode Dateien des \JTXC{}.
\begin{figure}[H]
\centering
\begin{tikzpicture}
% Define the styles for the nodes and arrows
\tikzstyle{rect} = [rectangle, draw, text width=13cm, align=center, minimum height=1.5cm, inner sep=3pt]
\tikzstyle{header} = [font=\large, anchor=north]
\tikzstyle{content} = [font=\footnotesize, anchor=north]
\tikzstyle{arrow} = [->, thick]
% Define the coordinates for the nodes
\node[rect] (rect1) at (0,0) {
\begin{tabular}{c}
\textbf{Bootstrap Classloader} \\
\footnotesize{Interne \ac{JDK} Klassen, Standard Library}
\end{tabular}
};
\node[rect] (rect2) at (0, -2.5) {
\begin{tabular}{c}
\textbf{Extension Classloader} \\
\footnotesize{\ac{JDK} Erweitungen, überlicherweise im \lstinline|$JAVA_HOME/lib/ext| Verzeichnis}
\end{tabular}
};
\node[rect] (rect3) at (0, -5) {
\begin{tabular}{c}
\textbf{Application Classloader} \\
\footnotesize{Klassen aus dem Classpath}
\end{tabular}
};
\node[rect] (rect4) at (0, -7.5) {
\begin{tabular}{c}
\textbf{Directory Classloader} \\
\footnotesize{Klassen, die dem Compiler mit -cp übergeben werden}
\end{tabular}
};
% Draw the arrows connecting the rectangles
\draw[arrow] (rect2.north) -- (rect1.south);
\draw[arrow] (rect3.north) -- (rect2.south);
\draw[arrow] (rect4.north) -- (rect3.south);
\end{tikzpicture}
\caption{Die Classloader Hierarchie des \JTXC{} (vgl. \cite{nero_all_2023})}
\label{fig:classloader}
\end{figure}
Da durch dieses Vorgehen auch die Klassen aus dem \ac{JVM} Classpath vom Classloader und damit vom \JTXC{} berücksichtigt werden, kann man diese importieren, ohne sie im Classpath angegeben zu müssen. Dies ist ein unerwünschtes Verhalten, da der Compiler so Klassen akzeptiert, die gegebenenfalls nicht vom Programmierer gewünscht sind, was zu Verwirrung führen kann. Ein Beispiel hierzu ist in \autoref{lst:jvm_classpath} zu sehen. Dieser Code kompiliert mit dem Befehl \lstinline|java -jar JavaTXCompiler.jar Main.jav|\footnote{JavaTXCompiler.jar ist hier der \JTXC{} in Form eines JAR Archivs} korrekt, obwohl die Klasse \texttt{com.google.common.math.IntMath} weder im Classpath angegeben ist, noch in der Standardbibliothek vorhanden ist. Sie ist jedoch im JAR Archiv des \JTXC{} und damit im \ac{JVM} Classpath zur Ausführung des Compilers vorhanden, da der \JTXC{} die Google Guava Bibliothek verwendet.
\begin{lstlisting}[language=java, caption={Verwenden von Klassen im JVM Classpath}, label={lst:jvm_classpath}]
import com.google.common.math.IntMath;
import java.lang.String;
class Main{
return2(){
return IntMath.checkedAdd(1, 1);
}
}
\end{lstlisting}
Dieses Verhalten ist vor allem auch für das Projekt \JTXCinJTX{} problematisch, da der \JTXC{} und der \JTXCinJTX{} die gleichen Klassen/Klassenhierarchie verwenden und somit die Möglichkeit besteht, dass der Compiler die Klassen des \JTXC{} verwendet, anstatt die des \JTXCinJTX.
Die Lösung dieses Problems ist glücklicherweise unkompliziert. Es muss lediglich die ClassLoader Hierarchie so angepasst werden, dass der Application Classloader übersprungen wird. Diese Änderung ist in \autoref{fig:classloader_no_application} zu sehen. Der Directory Classloader ruft nun direkt den Extension Classloader auf. So können weiterhin Klassen aus der Java Standard Bibliothek verwendet werden, jedoch nicht mehr die Klassen im \ac{JVM} Classpath.
\begin{figure}[H]
\centering
\begin{tikzpicture}
% Define the styles for the nodes and arrows
\tikzstyle{rect} = [rectangle, draw, text width=13cm, align=center, minimum height=2cm, inner sep=5pt]
\tikzstyle{header} = [font=\large, anchor=north]
\tikzstyle{content} = [font=\footnotesize, anchor=north]
\tikzstyle{arrow} = [->, thick]
% Define the coordinates for the nodes
\node[rect] (rect1) at (0,0) {
\begin{tabular}{c}
\textbf{Bootstrap Classloader} \\
\footnotesize{Interne \ac{JDK} Klassen, Standard Library}
\end{tabular}
};
\node[rect] (rect2) at (0, -2.5) {
\begin{tabular}{c}
\textbf{Extension Classloader} \\
\footnotesize{\ac{JDK} Erweitungen, überlicherweise im \lstinline|$JAVA_HOME/lib/ext| Verzeichnis}
\end{tabular}
};
\node[rect, fill=gray!30] (rect3) at (0, -5) {
\begin{tabular}{c}
\textbf{Application Classloader} \\
\footnotesize{Klassen aus dem Classpath}
\end{tabular}
};
\node[rect] (rect4) at (0, -7.5) {
\begin{tabular}{c}
\textbf{Directory Classloader} \\
\footnotesize{Klassen, die dem Compiler mit -cp übergeben werden}
\end{tabular}
};
% Draw the arrows connecting the rectangles
\draw[arrow] (rect2.north) -- (rect1.south);
\draw[arrow] (rect4.east) to ++(1,0) to ++(0, 5) to (rect2.east);
\end{tikzpicture}
\caption{Die Classloader Hierarchie des \JTXC{} ohne den ApplicationClassLoader}
\label{fig:classloader_no_application}
\end{figure}
\subsection{Kompatibilität von Java-TX Funktionstypen und funktionalen Interfaces}
Das Problem beschreibt die Kompatibilität von \ac{Java-TX} Funktionstypen und funktionalen Interfaces, die seit Java 8 als Zieltypen für Lambda Ausdrücken dienen (vgl. \autoref{sec:lambda}). Ziel ist es, bestehende Java Bibliotheken, die mit funktionalen Interfaces arbeiten, mit Java-TX Funktionstypen verwenden zu können.
Die theoretische Lösung für dieses Problem wurde bereits 2017 in \cite{plumicke_introducing_2017}[Abschnitt 6] beschrieben. Die praktische Umsetzung gestaltet sich jedoch komplizierter als gedacht.
Ein Beispiel für eine Bibliothek, die ausgiebig funktionale Interfaces verwendet ist die sehr verbreitete Stream API, welche mit Java 8 eingeführt wurde \cite{baeldung_java_2016}. Streams erlauben es Daten mit deklarativem Code zu verarbeiten, was die Lesbarkeit und Wartbarkeit des Codes erhöht \cite{softwarealchemy_streamline_2024}.
\begin{lstlisting}[language=java, caption={Verwendung der Stream API in Java}, label={lst:java_functional_interface}]
import java.util.List;
import java.util.stream.Stream;
import java.util.function.Predicate;
public class ListUtils{
static List<Integer> getAllEvenNumbers(List<Integer> list){
List<Integer> result = list.stream().filter(x -> x % 2 == 0).toList();
return result;
}
}
\end{lstlisting}
In \autoref{lst:java_functional_interface} ist ein Beispielprogramm in Java zu sehen, welches die Stream API verwendet. Die Methode \texttt{getAllEvenNumbers} filtert alle ungeraden Zahlen aus einer Liste.
Dazu verwendet sie die Lambda Funktion \lstinline{x -> x % 2 == 0}, die folgendermaßen definiert ist:
\[
\text{isEven}(n)\footnote{Im Quellcode hat der Lambda Ausdruck keinen Namen} =
\begin{cases}
true, & \text{wenn } n \mod 2 = 0 \\
false, & \text{sonst}
\end{cases}
\]
Die \texttt{filter} Methode von \texttt{java.util.stream.Stream} lässt nur Werte passieren, für die die angegebene Funktion zu \glqq{}true\grqq{} evaluiert. Zuletzt werden diese Werte in einer Liste gesammelt und zurückgegeben.
Der \gls{javac} Compiler inferiert für den Lambda Ausdruck \lstinline|x -> x % 2 == 0| den Typ \\
\texttt{java.util.function.Predicate<Integer>}, da explizit dieser Typ von der \texttt{filter} Methode erwartet wird.
\begin{verbatim}
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> filter(Predicate<? super T> predicate);
...
}
\end{verbatim}
In \ac{Java-TX} kompilierte dieser Code zu Beginn der Studienarbeit nicht, da der \JTXC{} den Typ \lstinline|Fun1$$<Integer, Boolean>| für den Lambda Ausdruck inferiert hat. Der \JTXC{} würde also den Lambda Ausdruck als Funktionstypen interpretieren, anstatt als funktionalen Interface, was soweit ja korrekt ist.
Obwohl die beiden Typen semantisch äquivalent sind, sind sie auf Grund des nominalen Typsystems von Java und der \ac{JVM} jedoch nicht austauschbar. Es kommt also zu einem Laufzeitfehler.
Dieses Problem ist von hoher Relevanz, da sämtliche Methoden aus Java Bibliotheken, die funktionalen Interfaces verwenden, so in \ac{Java-TX} nicht verwendet werden können.
Im Laufe der Arbeit wurde zumindest eine Teillösung dieses Problems implementiert, sodass der Code in \autoref{lst:java_functional_interface} kompiliert und lauffähig ist. Der \JTXC{} generiert in solch einem Fall nun den Code für das korrekte funktionale Interface statt dem \texttt{FunN\$\$} Typen. Das funktioniert allerdings nur, wenn der Lambda Ausdruck wie bei \autoref{lst:java_functional_interface} direkt im Funktionsaufruf steht.
Der Beispielcode in \autoref{lst:javatx_not_working_1} funktioniert also nicht und stellt aktuell noch ein Problem dar.
\begin{lstlisting}[language=java, caption={Aktuell nicht lauffähiger \ac{Java-TX} Code I}, label={lst:javatx_not_working_1}]
import java.util.List;
import java.lang.Integer;
import java.lang.Boolean;
import java.util.stream.Stream;
import java.util.function.Predicate;
public class ListUtils{
static getAllEvenNumbers(list){
// Java-TX inferiert hier Fun1$$<Integer, Boolean>
var func = x -> x % 2 == 0;
// An dieser Stelle wird aber ein Predicate<Integer> erwartet -> Laufzeitfehler
var result = list.stream().filter(func).toList();
}
}
\end{lstlisting}
Das liegt daran, dass der Compiler zum Zeitpunkt der Initialisierung des Lambda Ausdrucks nicht weiß, in welchem Kontext der Ausdruck später verwendet werden soll. Betrachten wir zur Verdeutlichung des Problems die etwas komplexere Funktion \lstinline|uselessFunction| in \autoref{lst:javatx_not_working_2}.
Über die Sinnhaftigkeit dieses Codes lässt sich streiten. Die Funktion sollte im Entdefekt das gleiche Resultat wie die Funktion in \autoref{lst:java_functional_interface}, mit dem Unterschied das in der Resultatliste für jede Zahl der Wert \lstinline|true| zurückgegeben wird, liefern.
Das Problem ist nun, dass die selbe Lambda Funktion im Laufe der Funktion mit verschiedenen funktionalen Interfaces verwendet wird, was die Sache verkompliziert. So würde die \lstinline|map| Methode in Zeile 10 den Typ \lstinline|Function<Integer, Boolean>| erwarten, während die Filter Methode in Zeile 9 weiterhin \lstinline|Predicate<Integer>| erwartet.
Hier müsste vermutlich für alle Aufrufe eine separate Lambda Funktion mit dem richtigen Target Type im Bytecode erstellt werden. Allerdings erfordert dies vermutlich eine größere Änderung am Compiler und konnte aktuell noch nicht umgesetzt werden.
\begin{lstlisting}[language=java, caption={Aktuell nicht lauffähiger \ac{Java-TX} Code II}, label={lst:javatx_not_working_2}]
import java.util.List;
import java.lang.Integer;
import java.lang.Boolean;
import java.util.stream.Stream;
import java.util.function.Predicate;
import java.util.function.Function;
public class ListUtils{
static uselessFunction(list){
// Java-TX inferiert hier Fun1$$<Integer, Boolean>
var func = x -> x % 2 == 0;
// An dieser Stelle wird ein Predicate<Integer> erwartet
var result1 = list.stream().filter(func);
// An dieser Stelle wird ein Function<Integer, Boolean> erwartet
var result2 = result1.map(func).toList();
return result2;
}
}
\end{lstlisting}
\subsection{Überschreiben von Methoden mit primitiven Datentypen}
Ein weiteres Problem, ist das Überschreiben von Methoden mit primitiven Datentypen.
Im Vergleich zu Java unterstützt \ac{Java-TX} nur Referenztypen, keine primitiven Datentypen. Die Grammatik erlaubt zwar die Verwendung von primitiven Datentypen, diese werden jedoch intern in Referenztypen umgewandelt. In \autoref{fig:primitive_types_javatx} sind auf der linken Seite einige Initialisierungen von primitiven Datentypen in \ac{Java-TX} gezeigt. Auf der rechten Seite gegenübergestellt ist der Code, wie er im Compiler intern verarbeitet und später auch im Bytecode generiert wird. Der Compiler generiert also für jeden primitiven Datentypen den entsprechenden Wrappertyp. Daher ist es auch bei der Verwendung von primitiven Typen notwendig die entsprechenden Wrapperklasse zu importieren.
\begin{figure}[h]
\centering
\begin{minipage}[t]{0.45\textwidth}
\centering
\begin{lstlisting}
import java.lang.Integer;
import java.lang.Boolean;
import java.lang.Float;
class PrimitiveTypes{
int i = 5;
boolean b = true;
float f = 10.5f;
}
\end{lstlisting}
\end{minipage}%
\hfill\vrule\hfill
\begin{minipage}[t]{0.45\textwidth}
\centering
\begin{lstlisting}
import java.lang.Integer;
import java.lang.Boolean;
import java.lang.Float;
class PrimitiveTypes{
Integer i = 5;
Boolean b = true;
Float f = 10.5f;
}
\end{lstlisting}
\end{minipage}%
\caption{Primitive Datentypen in Java-TX}
\label{fig:primitive_types_javatx}
\end{figure}
Diese Eigenschaft von \ac{Java-TX} führte im Zusammenhang mit der Überladung von Java Methoden, deren Rückgabewert oder Parameter primitive Datentypen sind, zu einem Problem. Nehmen wir als Beispiel den Code in \autoref{lst:primitive_types}. Die Methode \texttt{hashCode} wird von der Mutterklasse \texttt{Object}\footnote{In Java erben alle Klassen implizit von Object} geerbt.
Sie hat folgende Signatur:
\begin{verbatim}
int hashCode();
\end{verbatim}
Da sowohl der Name, als auch die Parameterliste der Methode übereinstimmt, würde man erwarten, dass diese von der Klasse \texttt{Foo} überschrieben wird.
\begin{lstlisting}[language=java, caption={Überschreiben von Methoden mit primitiven Datentypen in Java-TX}, label={lst:primitive_types}]
import java.lang.Integer;
public class Foo{
public hashCode(){
return 42;
}
}
\end{lstlisting}
Stattdessen inferiert der Compiler allerdings die Typen in \autoref{lst:primitive_types_infer} für die Methode \texttt{hashCode} in \autoref{lst:primitive_types}. Dies ist Aufgrund der Tatsache, dass primitive Datentypen in \ac{Java-TX} automatisch mit dem Wrappertyp ersetzt werden, logisch.
\begin{lstlisting}[language=java, caption={Ergebnis der Typinferenz für die Methode \texttt{hashCode} in Java-TX}, label={lst:primitive_types_infer}]
import java.lang.Integer;
public class Foo{
public java.lang.Integer hashCode(){
return 42;
}
}
\end{lstlisting}
Im Bytecode führt dies allerdings anstatt einer Überschreibung zu einer Überladung der Methode \lstinline|hashCode|, da der Rückgabetyp in \autoref{lst:primitive_types_infer} nicht mit dem Rückgabetyp in \autoref{lst:primitive_types} übereinstimmt, schließlich sind \texttt{int} und \texttt{java.lang.Integer} trotz Autoboxing unterschiedliche Typen (mehr dazu in \cite{naftalin_java_2007}[S.6 ff]).
Denn obwohl Java die Überladung von Methoden anhand des Rückgabetyps nicht unterstützt, ist es in Java Bytecode durchaus möglich. Dies macht sich Java z.B. für das kovariante Überladen von Methoden zunutze. Seit Java 5 ist es möglich, dass eine Methode in einer Subklasse einen Rückgabetyp hat, der ein Subtyp des Rückgabetyps der Methode in der Superklasse ist \cite{naftalin_java_2007}[S. 49]. Dazu sei zunächst die Signatur der Methode \lstinline|clone| in der Klasse \lstinline|Object| gegeben, welche kein Parameter hat und ein Objekt vom Typ \lstinline|Object| zurückgibt:
\begin{verbatim}
class Object{
...
protected Object clone(){...}
}
\end{verbatim}
Es ist nun möglich, die Methode \lstinline|clone| in einer Subklasse zu überschreiben und den Rückgabetyp zu spezialisieren. In \autoref{lst:covariant_overloading} wird die Methode \lstinline|clone| in der Klasse \lstinline|A| überschrieben und der Rückgabetyp auf \lstinline|A| spezialisiert.
\begin{lstlisting}[language=java, caption={Kovariante Methodenüberladung in Java}, label={lst:covariant_overloading}]
class A{
...
@Override
public A clone(){...}
}
\end{lstlisting}
Dies wird durch eine Bridge Methode ermöglicht, die sich den Fakt zunutze macht, dass die JVM Methoden anhand des Rückgabetyps unterscheidet und somit überladen kann. Der Compiler erzeugt also eine Bridge Methode, mit der Signatur \lstinline|Object clone()|, die die Methode \lstinline|A clone()| aufruft. In \autoref{lst:covariant_overloading_bytecode} ist dazu der dekompilierte Bytecode der Klasse \lstinline|A| gegeben.
\newpage
\begin{lstlisting}[language=java, caption={Dekompilierter Bytecode der Klasse A}, label={lst:covariant_overloading_bytecode}]
class A{
...
public A clone(){...}
public Object clone(){
return this.clone(); //Aufruf der Methode clone():A
}
}
\end{lstlisting}
In unserem Bug ist dies dies allerdings nicht das gewünschte Verhalten, da eine Überladung anhand des Rückgabewerts nicht möglich sein sollte. In Java werden Überladungen anhand des Rückgabewerts vom Compiler abgelehnt, da nicht immer ersichtlich ist, welche Methode aufgerufen werden soll. Außerdem wären Überladungen von Methoden welche primitive Typen verwenden in \ac{Java-TX} gänzlich unmöglich, weil selbst die explizite Angabe des Typs \lstinline|int| vom Compiler in \lstinline|java.lang.Integer| umgewandelt werden würde.
Um dieses Problem zu lösen wird aktuell eine einfache Substitution verwendet. Wenn der Compiler eine Überladung erkennt und der Rückgabetyp oder ein Parametertyp der Superklasse ein primitiver Datentyp ist, wird der dazugehörige Wrappertyp durch den jeweiligen primitiven Typen ersetzt. Dadurch funktioniert die Überschreibung von Methoden mit primitiven Datentypen korrekt. Da es aber noch einige Bugs mit dieser Implementierung gibt, bleibt abzuwarten, ob dies die endgültige Lösung ist.
\subsection{Korrekter Methodenaufruf für überladene Methoden mit Subtypen als Parameter}
Dieser Bug ist zum Zeitpunkt der Abgabe dieser Arbeit noch nicht behoben. Er tritt vor allem im Zusammenhang mit dem Visitor-Pattern auf, welches im \JTXC{} ausgiebig benutzt wird. Der Compiler ruft nicht immer die korrekte Methode auf, wenn mehrere potenziell korrekte Methoden zur Auswahl stehen. Sehen wir uns dazu an, wie Java mit diesem Problem umgeht. Dazu sei der Code in \autoref{lst:overloaded_methods} gegeben. Die Main Funktion ruft dabei die Methode \lstinline|visit| der Klasse \lstinline|Visitor| mit einer Instanz der Klasse \lstinline|java.lang.Integer| (bzw. int, was aber geboxed wird \cite{naftalin_java_2007}[S.6 ff]) auf. Die \lstinline|visit| Methode ist dreimal überladen, einmal mit \lstinline|java.lang.Object|, einmal mit \lstinline|java.lang.Number| und einmal mit einem \lstinline|java.lang.Integer|.
Die Frage ist nun, welche dieser Überladungen aufgerufen werden sollte.
Theoretisch wäre jeder Aufruf korrekt, da \lstinline|java.lang.Integer| sowohl von \lstinline|java.lang.Number| als auch von \lstinline|java.lang.Object| erbt. Das Verhalten in so einem Fall ist in \cite{gosling_java_2005}[Abschnitt 15.12.2.5] beschrieben. Java wählt in einem solchen Fall die spezifischste Methode, also die Methode, die den spezifischsten Typen als Parameter hat. In diesem Fall wäre das die Methode, mit der Signatur \lstinline|void visit(Integer i)|.
Dies bestätigt sich auch, wenn der Code in \autoref{lst:overloaded_methods} kompiliert und ausgeführt wird. Die Ausgabe ist wie erwartet \lstinline|"Integer"|.
\begin{lstlisting}[language=java, caption={Überladene Methoden in Java}, label={lst:overloaded_methods}]
class Main{
public static void main(String[] args){
Visitor v = new Visitor();
v.visit(1);
}
}
class Visitor{
public void visit(Object o){
System.out.println("Object");
}
public void visit(Number n){
System.out.println("Number");
}
public void visit(Integer i){
System.out.println("Integer");
}
}
\end{lstlisting}
Dieses Verhalten wäre vor allem aus Kompatibilitätsgründen auch in \ac{Java-TX} wünschenswert. Hier kommt es aktuell aber zu einem Fehler. Wenn der Code mehrmals kompiliert wird, wählt der Compiler jedes Mal eine andere Methode. Die Ausgabe ist also nicht deterministisch. Dies ist dadurch zu ergründen, dass der Typinferenzalgorithmus alle 3 Lösungen für den Methodenaufruf findet, aktuell aber nicht berücksichtigt, dass die Parameter der Methoden Subtypen sein können. Die Lösungen werden also als gleichwertig angesehen. Der Bytecodegenerator wählt dann aktuell die erste Lösung und verwirft die restlichen. Da der Typinferenzalgorithmus auf mehreren Threads parallel ausgeführt wird, kann sich die Reihenfolge der Ergebnisse ändern und somit auch das Ergebnis des Bytecodegenerators. Daher werden bei mehreren Kompilierungen unterschiedliche Ergebnisse erzielt.
Leider konnte das Problem bisher nicht gelöst werden, da es erst relativ spät bemerkt wurde. In einer späteren Version des Compilers sollte sich \ac{Java-TX} in diesem Punkt aber wie Java verhalten, so dass auch das Visitor Pattern in \ac{Java-TX} korrekt funktioniert.
\subsection{Weitere Bugs und fehlende Features}
Neben diesen umfangreich beschriebenen Problemen, gab es noch viele weitere Probleme, die hier nur kurz aufgeführt werden.
\begin{enumerate}
\item Die Access Modifier wurden nicht korrekt auf die Methoden angewendet. Alle Methoden wurden mit dem Access Modifier \lstinline|public| deklariert. Wenn ein anderer Access Modifier verwendet wurde, wurden dieser einfach hinzugefügt. So konnte es zu Signaturen wie \lstinline|public private void foo()| kommen.
\item Die Fehlermeldungen des Compilers wurden verbessert. Es wird nun angezeigt in welcher Zeile und Datei ein Constraint erstellt wurde.
\item Überladene Konstruktoren konnten nicht aufgerufen werden.
\item If-Statements ohne Blöcke wurden teilweise nicht korrekt verarbeitet.
\item \lstinline|toString()| und andere Methoden von \lstinline|Object| konnten auf Interfaces nicht aufgerufen werden.
\item Die @Override Annotation bei Methoden führte zu einem Fehler. Annotations wird nun ignoriert.
\item Die Typinferenz war für die neu hinzugefügte For\-Each Schleife fehlerhaft.
\item Es gab einige Probleme mit Interfaces, welche behoben wurden.
\item Der Bytecode beim Aufruf von statischen Methoden war fehlerhaft.
\item Bei der Überschreibung von vererbten Methoden mussten die Parametertypen den gleichen Namen haben.
\end{enumerate}
Zusätzlich sind auch einige Features aufgefallen, die bis zum aktuellen Stand noch nicht implementiert wurden.
\begin{enumerate}
\item Arrays und damit auch die Main Funktion sind nicht implementiert
\item Exceptions sind nur sehr rudimentär implementiert, z.B. sind checked Exceptions aktuell nicht möglich
\item Subtypisierung bei Funktionstypen funktioniert noch nicht wie es sollte
\item Der Funktionstyp FunVoidN\$\$ für Funktionstypen die void zurückgeben ist aktuell noch nicht umgesetzt
\end{enumerate}