Jawa
String getParamName(String param) throws Exception {
StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();
String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
String javapLine = javapLines.get(n);
if (byteCodeStart > -1 && (javapLine == null || "".equals(javapLine))) {
break;
}
Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
if (byteCodeIndexMatcher.find()) {
byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
} else if (javapLine.contains("line " + lineNum + ":")) {
byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
}
}
int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) {
if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
varLoadIndex = i;
continue;
}
if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) {
varTableIndex = i;
break;
}
}
String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
return null;
}
int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));
if (varName.find()) {
return varName.group(1);
}
j++;
}
return null;
}
Ini saat ini bekerja dengan beberapa gotcha:
- Jika Anda menggunakan IDE untuk mengkompilasi ini, itu mungkin tidak berfungsi kecuali dijalankan sebagai Admin (tergantung di mana file kelas sementara disimpan)
- Anda harus mengkompilasi menggunakan
javac
dengan -g
bendera. Ini menghasilkan semua informasi debugging termasuk nama variabel lokal di file kelas yang dikompilasi.
- Ini menggunakan Java API internal
com.sun.tools.javap
yang mem-parsing bytecode dari classfile dan menghasilkan hasil yang dapat dibaca manusia. API ini hanya dapat diakses di perpustakaan JDK sehingga Anda harus menggunakan runtime java JDK atau menambahkan tools.jar ke classpath Anda.
Ini sekarang harus bekerja bahkan jika metode ini dipanggil beberapa kali dalam program. Sayangnya itu belum berfungsi jika Anda memiliki banyak pemanggilan dalam satu baris. (Untuk yang melakukannya, lihat di bawah)
Cobalah online!
Penjelasan
StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();
String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";
Bagian pertama ini mendapat beberapa informasi umum tentang kelas apa kita berada dan apa nama fungsinya. Ini dilakukan dengan membuat pengecualian dan mem-parsing 2 entri pertama dari jejak stack.
java.lang.Exception
at E.getParamName(E.java:28)
at E.main(E.java:17)
Entri pertama adalah baris di mana pengecualian dilemparkan di mana kita dapat mengambil methodName dari dan entri kedua adalah tempat fungsi dipanggil.
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
Pada baris ini kita mengeksekusi javap executable yang datang dengan JDK. Program ini mem-parsing file kelas (bytecode) dan menyajikan hasil yang dapat dibaca manusia. Kami akan menggunakan ini untuk "parsing" yang belum sempurna.
List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
String javapLine = javapLines.get(n);
if (byteCodeStart > -1 && (javapLine == null || "".equals(javapLine))) {
break;
}
Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
if (byteCodeIndexMatcher.find()) {
byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
} else if (javapLine.contains("line " + lineNum + ":")) {
byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
}
}
Kami melakukan beberapa hal berbeda di sini. Pertama, kita membaca baris keluaran javap demi baris ke dalam daftar. Kedua kita membuat peta indeks garis bytecode ke indeks garis javap. Ini membantu kita nanti untuk menentukan metode doa mana yang ingin kita analisis. Akhirnya kita menggunakan nomor baris yang diketahui dari jejak tumpukan untuk menentukan indeks garis bytecode yang ingin kita lihat.
int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) {
if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
varLoadIndex = i;
continue;
}
if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) {
varTableIndex = i;
break;
}
}
Di sini kita mengulangi garis javap sekali lagi untuk menemukan tempat di mana metode kita dipanggil dan di mana Tabel Variabel Lokal dimulai. Kita perlu baris tempat metode dipanggil karena baris sebelum itu berisi panggilan untuk memuat variabel dan mengidentifikasi variabel mana (berdasarkan indeks) untuk memuat. Tabel Variabel Lokal membantu kita mencari nama variabel berdasarkan indeks yang kita ambil.
String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
return null;
}
Bagian ini sebenarnya mengurai panggilan beban untuk mendapatkan indeks variabel. Ini dapat membuang pengecualian jika fungsi tersebut tidak benar-benar dipanggil dengan variabel sehingga kita dapat mengembalikan nol di sini.
int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));
if (varName.find()) {
return varName.group(1);
}
j++;
}
return null;
Akhirnya kita menguraikan nama variabel dari baris di Tabel Variabel Lokal. Kembalikan nol jika tidak ditemukan walaupun saya tidak melihat alasan mengapa ini harus terjadi.
Menyatukan semuanya
public static void main(java.lang.String[]);
Code:
...
18: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: aload_2
23: invokevirtual #25 // Method getParamName:(Ljava/lang/String;)Ljava/lang/String;
...
LineNumberTable:
...
line 17: 18
line 18: 29
line 19: 40
...
LocalVariableTable:
Start Length Slot Name Signature
0 83 0 args [Ljava/lang/String;
8 75 1 e LE;
11 72 2 str Ljava/lang/String;
14 69 3 str2 Ljava/lang/String;
18 65 4 str4 Ljava/lang/String;
77 5 5 e1 Ljava/lang/Exception;
Ini pada dasarnya adalah apa yang kita lihat. Dalam kode contoh pemanggilan pertama adalah baris 17. baris 17 di LineNumberTable menunjukkan bahwa awal baris itu adalah bytecode indeks baris 18. Itu adalah System.out
beban. Kemudian kita memiliki aload_2
tepat sebelum pemanggilan metode sehingga kita mencari variabel dalam slot 2 dari LocalVariableTable yang str
dalam hal ini.
Untuk bersenang-senang, inilah yang menangani beberapa panggilan fungsi pada saluran yang sama. Ini menyebabkan fungsi tidak idempoten tapi itu intinya. Cobalah online!