ASM ไม่ได้จัดเตรียมวิธีที่สะอาดตาในการทำเช่นนี้ แต่เป็นไปได้หากคุณยินดีที่จะกำหนดคลาสใหม่ในแพ็คเกจ org.objectweb.asm
(หรือใช้การสะท้อนกลับเพื่อเข้าถึงสมาชิกแพ็คเกจส่วนตัว) สิ่งนี้ไม่เหมาะเพราะจะทำให้ต้องอาศัยรายละเอียดการใช้งานของ ASM แต่เป็นสิ่งที่ดีที่สุดที่เราสามารถทำได้ (หากคุณทราบวิธีที่ไม่แฮ็ก โปรดเพิ่มเป็นคำตอบอื่น)
บางสิ่งที่ไม่ได้ผล
ClassWriter เปิดเผย newConst
(และตัวแปรสำหรับประเภทรายการพูลคงที่อื่นๆ) เพื่ออนุญาตให้นำแอตทริบิวต์ที่กำหนดเองไปใช้ เนื่องจาก ASM จะนำรายการพูลคงที่กลับมาใช้ใหม่ คุณอาจถือว่าคุณสามารถเติมพูลคงที่ล่วงหน้าตามลำดับที่คุณต้องการได้โดยการโทร newConst
และเพื่อนๆ อย่างไรก็ตาม รายการพูลคงที่จำนวนมากอ้างอิงถึงรายการพูลคงที่อื่นๆ (โดยเฉพาะรายการ Utf8 ซึ่งอ้างอิงโดยรายการสตริงและคลาส) และวิธีการเหล่านี้จะเพิ่มรายการอ้างอิงโดยอัตโนมัติหากไม่มีอยู่ ดังนั้นจึงเป็นไปไม่ได้ที่จะใส่ค่าคงที่ String ก่อน Utf8 ที่อ้างอิงถึง เป็นต้น วิธีการเหล่านี้สามารถแทนที่ได้ แต่การทำเช่นนี้ไม่ได้ช่วยอะไร เนื่องจากลักษณะการทำงานนี้ถูกอบเข้าสู่วิธีการแพ็คเกจแบบส่วนตัวหรือแบบส่วนตัวที่พวกเขามอบหมายให้
โพสต์นี้ แนะนำให้เรียงลำดับโครงสร้างข้อมูลภายในของ ClassWriter ในการโอเวอร์โหลด visitEnd
. สิ่งนี้ใช้ไม่ได้ด้วยเหตุผลสองประการ ประการแรก visitEnd
ถือเป็นที่สิ้นสุด (บางทีอาจไม่ได้ย้อนกลับไปในปี 2548 เมื่อมีการเขียนโพสต์นั้น) ประการที่สอง ClassWriter ปล่อยไบต์ของคลาสในระหว่างการเยี่ยมชม ดังนั้นเมื่อถึงเวลาเรียก visitEnd
พูลคงที่จะถูกเขียนเป็นไบต์แล้ว และดัชนีพูลคงที่จะถูกรวมเข้าเป็นไบต์ของโค้ดแล้ว
การแก้ไขปัญหา
การแก้ปัญหานี้ต้องใช้การเขียนในชั้นเรียนสองรอบ ขั้นแรก เราจะเขียนคลาสตามปกติ (รวมถึงการแปลงอื่นๆ) จากนั้นใช้ ClassWriter อื่นที่มีพูลคงที่ที่เติมไว้ล่วงหน้าเพื่อแยกวิเคราะห์และเขียนผลลัพธ์ของรอบแรกใหม่ เนื่องจาก ClassWriter สร้างไบต์พูลคงที่ในขณะที่ทำงาน เราจึงต้องดำเนินการด้วยตนเองก่อนที่จะเริ่มแยกวิเคราะห์และเขียนครั้งที่สอง เราจะสรุปการแยกวิเคราะห์/เขียนครั้งที่สองในเมธอด toByteArray
ของ ClassWriter ตัวแรก
นี่คือรหัส การเรียงลำดับจริงเกิดขึ้นในวิธี sortItems
ในที่นี้ เรากำลังเรียงลำดับตามจำนวนครั้งที่เกิดขึ้นโดยเป็นตัวถูกดำเนินการ ldc
/ldc_w
(รวบรวมโดย MethodVisitor โปรดทราบว่า visitMethod
ถือเป็นที่สิ้นสุด ดังนั้นจึงต้องแยกจากกัน) หากคุณต้องการใช้การเรียงลำดับอื่น ให้เปลี่ยน sortItems
และเพิ่มฟิลด์เพื่อจัดเก็บการเรียงลำดับของคุณ
package org.objectweb.asm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class ConstantPoolSortingClassWriter extends ClassWriter {
private final int flags;
Map<Item, Integer> constantHistogram; //initialized by ConstantHistogrammer
public ConstantPoolSortingClassWriter(int flags) {
super(flags);
this.flags = flags;
}
@Override
public byte[] toByteArray() {
byte[] bytes = super.toByteArray();
List<Item> cst = new ArrayList<>();
for (Item i : items)
for (Item j = i; j != null; j = j.next) {
//exclude ASM's internal bookkeeping
if (j.type == TYPE_NORMAL || j.type == TYPE_UNINIT ||
j.type == TYPE_MERGED || j.type == BSM)
continue;
if (j.type == CLASS)
j.intVal = 0; //for ASM's InnerClesses tracking
cst.add(j);
}
sortItems(cst);
ClassWriter target = new ClassWriter(flags);
//ClassWriter.put is private, so we have to do the insert manually
//we don't bother resizing the hashtable
for (int i = 0; i < cst.size(); ++i) {
Item item = cst.get(i);
item.index = target.index++;
if (item.type == LONG || item.type == DOUBLE)
target.index++;
int hash = item.hashCode % target.items.length;
item.next = target.items[hash];
target.items[hash] = item;
}
//because we didn't call newFooItem, we need to manually write pool bytes
//we can call newFoo to find existing items, though
for (Item i : cst) {
if (i.type == UTF8)
target.pool.putByte(UTF8).putUTF8(i.strVal1);
if (i.type == CLASS || i.type == MTYPE || i.type == STR)
target.pool.putByte(i.type).putShort(target.newUTF8(i.strVal1));
if (i.type == IMETH || i.type == METH || i.type == FIELD)
target.pool.putByte(i.type).putShort(target.newClass(i.strVal1)).putShort(target.newNameType(i.strVal2, i.strVal3));
if (i.type == INT || i.type == FLOAT)
target.pool.putByte(i.type).putInt(i.intVal);
if (i.type == LONG || i.type == DOUBLE)
target.pool.putByte(i.type).putLong(i.longVal);
if (i.type == NAME_TYPE)
target.pool.putByte(i.type).putShort(target.newUTF8(i.strVal1)).putShort(target.newUTF8(i.strVal2));
if (i.type >= HANDLE_BASE && i.type < TYPE_NORMAL) {
int tag = i.type - HANDLE_BASE;
if (tag <= Opcodes.H_PUTSTATIC)
target.pool.putByte(HANDLE).putByte(tag).putShort(target.newField(i.strVal1, i.strVal2, i.strVal3));
else
target.pool.putByte(HANDLE).putByte(tag).putShort(target.newMethod(i.strVal1, i.strVal2, i.strVal3, tag == Opcodes.H_INVOKEINTERFACE));
}
if (i.type == INDY)
target.pool.putByte(INDY).putShort((int)i.longVal).putShort(target.newNameType(i.strVal1, i.strVal2));
}
//parse and rewrite with the new ClassWriter, constants presorted
ClassReader r = new ClassReader(bytes);
r.accept(target, 0);
return target.toByteArray();
}
private void sortItems(List<Item> items) {
items.forEach(i -> constantHistogram.putIfAbsent(i, 0));
//constants appearing more often come first, so we use as few ldc_w as possible
Collections.sort(items, Comparator.comparing(constantHistogram::get).reversed());
}
}
นี่คือ ConstantHistogrammer ซึ่งอยู่ใน org.objectweb.asm
จึงสามารถอ้างอิงถึง Item
ได้ การใช้งานนี้มีไว้สำหรับการเรียงลำดับ ldc
โดยเฉพาะ แต่จะสาธิตวิธีการเรียงลำดับแบบกำหนดเองอื่นๆ ตามข้อมูลจากไฟล์ .class
package org.objectweb.asm;
import java.util.HashMap;
import java.util.Map;
public final class ConstantHistogrammer extends ClassVisitor {
private final ConstantPoolSortingClassWriter cw;
private final Map<Item, Integer> constantHistogram = new HashMap<>();
public ConstantHistogrammer(ConstantPoolSortingClassWriter cw) {
super(Opcodes.ASM5, cw);
this.cw = cw;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new CollectLDC(super.visitMethod(access, name, desc, signature, exceptions));
}
@Override
public void visitEnd() {
cw.constantHistogram = constantHistogram;
super.visitEnd();
}
private final class CollectLDC extends MethodVisitor {
private CollectLDC(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitLdcInsn(Object cst) {
//we only care about things ldc can load
if (cst instanceof Integer || cst instanceof Float || cst instanceof String ||
cst instanceof Type || cst instanceof Handle)
constantHistogram.merge(cw.newConstItem(cst), 1, Integer::sum);
super.visitLdcInsn(cst);
}
}
}
สุดท้ายนี้ ต่อไปนี้คือวิธีที่คุณใช้ร่วมกัน:
byte[] inputBytes = Files.readAllBytes(input);
ClassReader cr = new ClassReader(inputBytes);
ConstantPoolSortingClassWriter cw = new ConstantPoolSortingClassWriter(0);
ConstantHistogrammer ch = new ConstantHistogrammer(cw);
ClassVisitor s = new SomeOtherClassVisitor(ch);
cr.accept(s, 0);
byte[] outputBytes = cw.toByteArray();
การแปลงที่ใช้โดย SomeOtherClassVisitor
จะเกิดขึ้นเฉพาะในการเข้าชมครั้งแรกเท่านั้น ไม่ใช่ในการเข้าชมครั้งที่สองภายใน cw.toByteArray()
ไม่มีชุดทดสอบสำหรับสิ่งนี้ แต่ฉันใช้การเรียงลำดับข้างต้นกับ rt.jar
จาก Oracle JDK 8u40 และ NetBeans 8.0.2 ทำงานได้ตามปกติโดยใช้ไฟล์คลาสที่แปลงแล้ว ดังนั้นอย่างน้อยที่สุดก็ถูกต้องเป็นส่วนใหญ่ (การแปลงบันทึกได้ 12,684 ไบต์ ซึ่งแทบจะไม่คุ้มค่าเลย)
รหัส มีให้ใช้งานในรูปแบบ Gist ภายใต้ใบอนุญาตเดียวกันกับ ASM เอง
person
Jeffrey Bosboom
schedule
16.03.2015