Wenn Sie alte Klassen instrumentieren, die keine Stackmaps haben, und ihre alte Versionsnummer beibehalten, wird es kein Problem geben, da sie von der JVM wie zuvor verarbeitet werden und keine Stackmaps benötigen. Dies bedeutet natürlich, dass Sie keine neueren Bytecode-Funktionen einfügen können.
Wenn Sie neuere Klassendateien mit gültigen Stackmaps vor der Umwandlung instrumentieren, werden diese Probleme nicht auftreten described by Antimony. So können Sie ASM verwenden, um stackmaps zu regenerieren:
byte[] bytecode = … // result of your instrumentation
ClassReader cr = new ClassReader(bytecode);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cr.accept(cw, ClassReader.SKIP_FRAMES);
bytecode = cw.toByteArray(); // with recalculated stack maps
Der Besucher API entwickelt wurde, mit einem Schriftsteller einfache Kaskadierung eines Lesers zu ermöglichen und nur Code fügen Sie diese Artefakte abfangen Sie ändern möchten.
Da wir wissen, dass wir die Stackmap Frames von Grund auf mit ClassWriter.COMPUTE_FRAMES
neu generieren werden, können wir ClassReader.SKIP_FRAMES
an den Leser übergeben, um es zu sagen, die Quellframes nicht zu verarbeiten, die wir sowieso ignorieren werden.
Es ist eine weitere Optimierung möglich, wenn wir wissen, dass sich die Klassenstruktur nicht ändert. Wir können den ClassReader
an den Konstruktor ClassWriter
übergeben, um einen Vorteil von der unveränderten Struktur, z. Der Zielkonstantenpool wird mit einer Kopie des Quellkonstantenpools initialisiert. Diese Option muss jedoch mit Vorsicht behandelt werden. Wenn wir Methoden überhaupt nicht abfangen, wird es ebenfalls optimiert, d. H. Der Code wird vollständig kopiert, ohne die Stapelrahmen neu zu berechnen. Also brauchen wir einen Besucher benutzerdefinierte Methode, so zu tun, dass der Code möglicherweise ändern könnte:
byte[] bytecode = … // result of your instrumentation
ClassReader cr = new ClassReader(bytecode);
// passing cr to ClassWriter to enable optimizations
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor writer=super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM5, writer) {
// not changing anything, just preventing code specific optimizations
};
}
}, ClassReader.SKIP_FRAMES);
bytecode = cw.toByteArray(); // with recalculated stack maps
So wie der Konstanten-Pool unverändert Artefakte kann direkt an die Ziel Bytecode kopiert werden, während die stackmap Rahmen noch neu berechnet bekommen.
Es gibt jedoch einige Vorbehalte. Stackmaps von Grund auf neu zu erstellen bedeutet, dass kein Wissen über die ursprüngliche Codestruktur oder die Art der Transformation verwendet wird. Z.B. Ein Compiler würde die formalen Typen von lokalen Variablendeklarationen kennen, wohingegen der ClassWriter
verschiedene tatsächliche Typen sehen könnte, für die er den gemeinsamen Basistyp finden muss. Diese Suche kann sehr teuer sein, weil das Laden von Klassen, die zurückgestellt wurden oder nicht einmal während der normalen Ausführung verwendet werden. Der resultierende Typ kann sich sogar von dem allgemeinen Typ unterscheiden, der im ursprünglichen Code deklariert wurde. Es wird ein korrekter Typ sein, aber die Verwendung von Klassen im resultierenden Code kann wieder geändert werden.
Wenn Sie die Instrumentierung in einer anderen Umgebung ausführen, können ASMs Versuche, die Klassen zum Ermitteln des allgemeinen Typs zu laden, fehlschlagen. Dann müssen Sie ClassWriter.getCommonSuperClass(…)
mit einer Implementierung überschreiben, die den Vorgang in dieser Umgebung ausführen kann. Dies ist auch der Ort, um Optimierungen hinzuzufügen, wenn Sie mehr über den Code wissen und ohne teure Suchen durch die Typhierarchie Antworten geben können.
Im Allgemeinen wird empfohlen, die alte Bibliothek so zu refaktorieren, dass ASM anstelle eines nachfolgenden Anpassungsschritts verwendet werden kann. Wie oben erläutert, kann ASM bei der Codetransformation mit einer Kette von ClassReader
und ClassWriter
mit aktivierten Optimierungen alle unveränderten Methoden einschließlich ihrer Stackmaps kopieren und nur die Stackmaps der tatsächlich geänderten Methoden neu berechnen. In dem obigen Code, der die Neuberechnung in einem nachfolgenden Schritt durchführt, mussten wir die Optimierung deaktivieren, da wir nicht mehr wissen, welche Methoden tatsächlich geändert wurden.
Der nächste logische Schritt wäre es, Stackmap-Handling in die Instrumentierung zu integrieren, da das Wissen über die eigentliche Transformation mehr als 99% der bestehenden Frames behalten und die anderen einfach anpassen kann, anstatt eine teure Neuberechnung zu benötigen kratzen.
Danke für das Tutorial; Ich hatte keine Ahnung, dass es so involviert war - ich hatte gehofft, ich könnte einfach in einer Klasse lesen und sie mit einer Stackmap umschreiben.Davon abgesehen wird der Basiscode mit dem J8-Compiler kompiliert (obwohl er bestenfalls nur J6-Code ist). Es gibt dann eine Persistenz-Bibliothek, die die Byte-Erweiterung durchführt - ich glaube, es fügt Felder und Getter/Setter hinzu und schreibt die Klassendatei neu. Angesichts dessen, was Sie gesagt haben, scheint es, dass ich eine Menge Arbeit haben würde, um sicherzustellen, dass die neuen Methoden und Felder die Kriterien erfüllen. Gibt es einen einfachen Weg zu validieren, wie viel Arbeit beteiligt wäre? –
Wenn ich den Code als J6-Code erzeuge/kompiliere, würde das meine Probleme lindern? Gibt es eine Möglichkeit, J7 + -Code als J6-Ziel zu kompilieren? Würde ich von der Verwendung von JEE7- oder JEE8-Konzepten eingeschränkt werden, indem ich mich auf J6-Code beschränke? Ich kann mir das nicht vorstellen, außer die JEE Apis sind für J7 + geschrieben. –
@Eric B. Sie können fast sicher nur ClassReader und ClassWriter verwenden und es sollte einfach funktionieren. Alle Probleme, die ich erwähnt habe, sind theoretische Probleme, die Sie im Code der realen Welt wahrscheinlich nicht sehen werden. – Antimony