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;
}
Cela fonctionne actuellement avec quelques pièges:
- Si vous utilisez un IDE pour le compiler, il peut ne pas fonctionner à moins qu'il ne soit exécuté en tant qu'administrateur (selon l'endroit où les fichiers de classe temporaires sont enregistrés)
- Vous devez compiler à l'aide
javac
de l' -g
indicateur. Cela génère toutes les informations de débogage, y compris les noms de variables locales dans le fichier de classe compilé.
- Cela utilise une API Java interne
com.sun.tools.javap
qui analyse le bytecode d'un fichier de classe et produit un résultat lisible par l'homme. Cette API n'est accessible que dans les bibliothèques JDK, vous devez donc utiliser le runtime JDK java ou ajouter tools.jar à votre chemin de classe.
Cela devrait maintenant fonctionner même si la méthode est appelée plusieurs fois dans le programme. Malheureusement, cela ne fonctionne pas encore si vous avez plusieurs appels sur une seule ligne. (Pour celui qui le fait, voir ci-dessous)
Essayez-le en ligne!
Explication
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";
Cette première partie contient des informations générales sur la classe dans laquelle nous nous trouvons et le nom de la fonction. Pour ce faire, créez une exception et analysez les 2 premières entrées de la trace de pile.
java.lang.Exception
at E.getParamName(E.java:28)
at E.main(E.java:17)
La première entrée est la ligne d'où l'exception est levée sur laquelle nous pouvons saisir le methodName et la deuxième entrée est l'endroit d'où la fonction a été appelée.
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
Dans cette ligne, nous exécutons l'exécutable javap fourni avec le JDK. Ce programme analyse le fichier de classe (bytecode) et présente un résultat lisible par l'homme. Nous l'utiliserons pour une "analyse" rudimentaire.
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));
}
}
Nous faisons deux choses différentes ici. Tout d'abord, nous lisons la sortie javap ligne par ligne dans une liste. Deuxièmement, nous créons une carte des index de ligne de bytecode aux index de ligne javap. Cela nous aide plus tard à déterminer quelle invocation de méthode nous voulons analyser. Enfin, nous utilisons le numéro de ligne connu de la trace de pile pour déterminer quel index de ligne de bytecode nous voulons regarder.
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;
}
}
Ici, nous parcourons les lignes javap une fois de plus afin de trouver l'endroit où notre méthode est invoquée et où commence la table de variables locales. Nous avons besoin de la ligne où la méthode est invoquée car la ligne avant elle contient l'appel pour charger la variable et identifie la variable (par index) à charger. La table des variables locales nous aide à rechercher le nom de la variable en fonction de l'index que nous avons récupéré.
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;
}
Cette partie analyse réellement l'appel de chargement pour obtenir l'index des variables. Cela peut lever une exception si la fonction n'est pas réellement appelée avec une variable afin que nous puissions retourner null ici.
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;
Enfin, nous analysons le nom de la variable à partir de la ligne de la table des variables locales. Renvoie null s'il n'est pas trouvé, même si je n'ai vu aucune raison pour laquelle cela devrait se produire.
Mettre tous ensemble
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;
C'est essentiellement ce que nous examinons. Dans l'exemple de code, la première invocation est la ligne 17. La ligne 17 dans le LineNumberTable montre que le début de cette ligne est l'index de la ligne de bytecode 18. C'est la System.out
charge. Ensuite, nous avons aload_2
juste avant l'appel de la méthode, nous recherchons donc la variable dans l'emplacement 2 du LocalVariableTable qui est str
dans ce cas.
Pour le plaisir, en voici un qui gère plusieurs appels de fonction sur la même ligne. Cela fait que la fonction n'est pas idempotente mais c'est un peu le point. Essayez-le en ligne!