Java
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;
}
Bu şu anda birkaç gotcha ile çalışıyor:
- Bunu derlemek için bir IDE kullanırsanız, Yönetici olarak çalıştırılmadığı sürece (geçici sınıf dosyalarının nereye kaydedildiğine bağlı olarak) çalışmayabilir.
- Sen kullanarak derlemek gerekir
javac
ile -g
bayrak. Bu, derlenmiş sınıf dosyasındaki yerel değişken adları da dahil olmak üzere tüm hata ayıklama bilgilerini oluşturur.
- Bu
com.sun.tools.javap
, bir sınıf dosyasının bayt kodunu ayrıştıran ve okunabilir bir sonuç üreten dahili bir Java API kullanır . Bu API'ya yalnızca JDK kitaplıklarından erişilebilir, bu nedenle JDK java çalışma zamanını kullanmalı veya sınıf yolunuza tools.jar eklemelisiniz.
Bu yöntem, yöntem programda birden çok kez çağrılsa bile çalışmalıdır. Tek bir satırda birden fazla çağrınız varsa maalesef henüz çalışmıyor. (Bunu yapan biri için aşağıya bakın)
Çevrimiçi deneyin!
açıklama
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";
Bu ilk bölüm hangi sınıfta olduğumuz ve fonksiyonun adı hakkında bazı genel bilgiler alır. Bu, bir istisna oluşturarak ve yığın izlemesinin ilk 2 girdisini ayrıştırarak gerçekleştirilir.
java.lang.Exception
at E.getParamName(E.java:28)
at E.main(E.java:17)
İlk giriş, methodName kaynağını alabileceğimiz özel durumun atıldığı, ikinci girdi ise işlevin çağrıldığı yerdir.
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
Bu satırda JDK ile gelen javap yürütülebilir dosyasını yürütüyoruz. Bu program sınıf dosyasını (bayt kodu) ayrıştırır ve insan tarafından okunabilir bir sonuç sunar. Bunu ilkel "ayrıştırma" için kullanacağız.
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));
}
}
Burada birkaç farklı şey yapıyoruz. İlk olarak, javap çıktısını satır satır bir liste halinde okuyoruz. İkincisi, javap hat indekslerine bir bytecode satır indeksleri haritası oluşturuyoruz. Bu, daha sonra hangi yöntem çağrısını analiz etmek istediğimizi belirlememize yardımcı olur. Son olarak hangi bayt kodu satır dizinine bakmak istediğimizi belirlemek için yığın izlemesindeki bilinen satır numarasını kullanıyoruz.
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;
}
}
Burada, yöntemimizin çağrıldığı yeri ve Yerel Değişken Tablosunun nerede başladığını bulmak için javap çizgileri üzerinde bir kez daha yineliyoruz. Yöntemin çağrıldığı satıra ihtiyacımız var çünkü değişkenin yüklenmesi çağrısını içermeden önceki satır ve hangi değişkenin (dizine göre) yükleneceğini tanımlar. Yerel Değişken Tablosu, yakaladığımız dizine bağlı olarak değişkenin adını aramamıza yardımcı olur.
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;
}
Bu bölüm aslında değişken indeksini almak için yük çağrısını ayrıştırmaktadır. İşlev aslında bir değişkenle çağrılmazsa bu bir istisna oluşturabilir, böylece burada null değerini döndürebiliriz.
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;
Son olarak, değişkenin adını Yerel Değişken Tablosundaki satırdan ayrıştırıyoruz. Bunun olması için hiçbir neden görmedim, ancak bulunamazsa null değerini döndürün.
Hepsini bir araya koy
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;
Temelde baktığımız şey bu. Örnek kodda, ilk çağrı 17. satırdır. LineNumberTable'daki satır 17, bu satırın başlangıcının bayt kodu satır dizini 18 olduğunu gösterir. Bu, System.out
yüktür. Sonra aload_2
yöntem çağrısından hemen önce var, bu nedenle LocalVariableTable'ın yuva 2'sinde str
bu durumda olan değişkeni ararız .
Eğlenmek için, aynı hatta birden fazla işlev çağrısını işleyen bir örnek. Bu, işlevin idempotent olmamasına neden olur, ancak bu bir nokta. Çevrimiçi deneyin!