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;
}
これは現在いくつかの落とし穴で動作します:
- IDEを使用してこれをコンパイルする場合、管理者として実行しない限り機能しない可能性があります(一時クラスファイルの保存場所によって異なります)
- フラグを使用
javac
してコンパイルする必要があります-g
。これにより、コンパイルされたクラスファイルにローカル変数名を含むすべてのデバッグ情報が生成されます。
- これは、
com.sun.tools.javap
クラスファイルのバイトコードを解析し、人間が読める結果を生成する内部Java API を使用します。このAPIはJDKライブラリでのみアクセスできるため、JDK javaランタイムを使用するか、tools.jarをクラスパスに追加する必要があります。
これは、プログラムでメソッドが複数回呼び出された場合でも機能するはずです。残念ながら、1行に複数の呼び出しがある場合、まだ機能しません。(そうするものについては、以下を参照してください)
オンラインでお試しください!
説明
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";
この最初の部分では、現在のクラスと関数の名前に関する一般的な情報を取得します。これは、例外を作成し、スタックトレースの最初の2つのエントリを解析することで実現されます。
java.lang.Exception
at E.getParamName(E.java:28)
at E.main(E.java:17)
最初のエントリはmethodNameを取得できる例外がスローされる行であり、2番目のエントリは関数が呼び出された場所です。
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
この行では、JDKに付属のjavap実行可能ファイルを実行しています。このプログラムは、クラスファイル(バイトコード)を解析し、人間が読み取れる結果を表示します。これを初歩的な「解析」に使用します。
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));
}
}
ここでは、いくつかの異なることを行っています。最初に、javap出力を1行ずつリストに読み込みます。次に、バイトコード行インデックスからjavap行インデックスへのマップを作成しています。これは、後で分析するメソッド呼び出しを決定するのに役立ちます。最後に、スタックトレースの既知の行番号を使用して、見たいバイトコード行インデックスを決定します。
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;
}
}
ここで、メソッドが呼び出されている場所とローカル変数テーブルが開始されている場所を見つけるために、javap行をもう一度繰り返します。メソッドを呼び出す行が必要なのは、その前の行に変数をロードする呼び出しが含まれており、ロードする変数を(インデックスによって)特定するためです。ローカル変数テーブルは、取得したインデックスに基づいて変数の名前を実際に検索するのに役立ちます。
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;
}
この部分は、実際にはロードコールを解析して変数インデックスを取得しています。関数が実際に変数で呼び出されない場合、これは例外をスローする可能性があるため、ここで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;
最後に、ローカル変数テーブルの行から変数の名前を解析します。これが発生する理由はわかりませんが、見つからない場合はnullを返します。
すべてを一緒に入れて
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;
これは基本的に私たちが見ているものです。コード例では、最初の呼び出しは17行目です。LineNumberTableの17行目は、その行の先頭がバイトコード行インデックス18であることを示していSystem.out
ます。これが負荷です。次にaload_2
、メソッド呼び出しの直前にあるためstr
、この場合はLocalVariableTableのスロット2で変数を探します。
楽しみのために、同じ行で複数の関数呼び出しを処理するものがあります。これにより、関数はi等ではなくなりますが、それは一種のポイントです。オンラインでお試しください!