2016-11-17 3 views
1

Je n'arrive pas à comprendre le positionnement des variables sur le bytecode Java ASMified. J'ai la Javacode suivante:Comprendre la position de variable locale dans le bytecode JVM sur

public class TryCatch { 
    public static void main(String[] args) { 
     String test1 = null; 
     try { 
      String test2 ="try-inside-begin"; 
      System.out.println("try-outside-begin"); 
      try { 
       System.out.println(test2); 
       System.out.println(test1.length()); 
       System.out.println("try-inside-end"); 
      } catch (NullPointerException e) { 
       test2 = "catch-inside: " + e.getMessage(); 
       throw new Exception(test2, e); 
      } 
      System.out.println("try-outside-end"); 
     } catch (Exception e) { 
      System.out.println("catch-outside: " + e.getMessage()); 
     } finally { 
      System.out.println("finally"); 
     } 
    } 
} 

qui devient le bytecode suivant pour main:

TRYCATCHBLOCK L0 L1 L2 java/lang/NullPointerException 
    TRYCATCHBLOCK L3 L4 L5 java/lang/Exception 
    TRYCATCHBLOCK L3 L4 L6 null 
    TRYCATCHBLOCK L5 L7 L6 null 
    TRYCATCHBLOCK L6 L8 L6 null 
L9 
    LINENUMBER 5 L9 
    ACONST_NULL 
    ASTORE 1 
L3 
    LINENUMBER 7 L3 
    LDC "try-inside-begin" 
    ASTORE 2 
L10 
    LINENUMBER 8 L10 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    LDC "try-outside-begin" 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L0 
    LINENUMBER 10 L0 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    ALOAD 2 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L11 
    LINENUMBER 11 L11 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    ALOAD 1 
    INVOKEVIRTUAL java/lang/String.length()I 
    INVOKEVIRTUAL java/io/PrintStream.println (I)V 
L12 
    LINENUMBER 12 L12 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    LDC "try-inside-end" 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L1 
    LINENUMBER 16 L1 
    GOTO L13 
L2 
    LINENUMBER 13 L2 
FRAME FULL [[Ljava/lang/String; java/lang/String java/lang/String] [java/lang/NullPointerException] 
    ASTORE 3 
L14 
    LINENUMBER 14 L14 
    NEW java/lang/StringBuilder 
    DUP 
    INVOKESPECIAL java/lang/StringBuilder.<init>()V 
    LDC "catch-inside: " 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    ALOAD 3 
    INVOKEVIRTUAL java/lang/NullPointerException.getMessage()Ljava/lang/String; 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String; 
    ASTORE 2 
L15 
    LINENUMBER 15 L15 
    NEW java/lang/Exception 
    DUP 
    ALOAD 2 
    ALOAD 3 
    INVOKESPECIAL java/lang/Exception.<init> (Ljava/lang/String;Ljava/lang/Throwable;)V 
    ATHROW 
L13 
    LINENUMBER 17 L13 
FRAME SAME 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    LDC "try-outside-end" 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L4 
    LINENUMBER 21 L4 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    LDC "finally" 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L16 
    LINENUMBER 22 L16 
    GOTO L17 
L5 
    LINENUMBER 18 L5 
FRAME FULL [[Ljava/lang/String; java/lang/String] [java/lang/Exception] 
    ASTORE 2 
L18 
    LINENUMBER 19 L18 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    NEW java/lang/StringBuilder 
    DUP 
    INVOKESPECIAL java/lang/StringBuilder.<init>()V 
    LDC "catch-outside: " 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    ALOAD 2 
    INVOKEVIRTUAL java/lang/Exception.getMessage()Ljava/lang/String; 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String; 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L7 
    LINENUMBER 21 L7 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    LDC "finally" 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
L19 
    LINENUMBER 22 L19 
    GOTO L17 
L6 
    LINENUMBER 21 L6 
FRAME SAME1 java/lang/Throwable 
    ASTORE 4 
L8 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    LDC "finally" 
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
    ALOAD 4 
    ATHROW 
L17 
    LINENUMBER 23 L17 
FRAME SAME 
    RETURN 
    MAXSTACK = 4 
    MAXLOCALS = 5 

Remarquez comment près du fond il y a ASTORE 4/ALOAD 4. Pourquoi est-ce 4 au lieu de 3? Comme la trame SAME1 est "les mêmes locales que la trame précédente et avec une seule valeur sur la pile" et la trame précédente n'a que deux locals (réf: FRAME FULL [[Ljava/lang/String; java/lang/String] [java/lang/Exception]).

J'ai lu the spec et mais il pas clair pour moi à partir de là, soit pourquoi il ne 3.

+0

Je suppose qu'il pourrait être 3, sauf les utilisations précédentes de 3 signifie qu'il est plus simple de le faire utiliser 4 et ne pas réutiliser 3. –

+1

Cette partie de la spécification n'est pas vraiment utile, car elle décrit le 'jsr' /' ret' obsolète mécanisme basé. Généralement, un compilateur peut utiliser autant de variables locales supplémentaires obsolètes qu'il le souhaite et [comme indiqué ici] (http://stackoverflow.com/a/25746587/2711488) et [ici] (http://stackoverflow.com/questions/6386917/2711488), le code de gestion des exceptions généré par 'javac' est loin d'être optimal. – Holger

+0

@Holger - Je suppose que cela signifie que si je construis un interpréteur bytecode, je ne peux pas me fier aux variables locales pour être demandées dans l'ordre? Par exemple. un frame courant actuellement à 3 vars locales peut demander le 5ème slot et ignorer 4. :-( –

Répondre

2

Le cadre de pile décrit l'état des variables locales et la pile d'opérandes au point où il apparaît. Les instructions ultérieures peuvent bien sûr modifier les choses comme d'habitude. Comme vous l'avez correctement identifié, le cadre de pile de L6 indique qu'il existe deux variables locales lorsque le flux de contrôle atteint L6. L'instruction suivante est stockée dans l'emplacement 4, ce qui est parfaitement légal.

Il peut être utile de comprendre le but de la carte de la pile. À l'origine, il n'y avait aucune carte de pile et le vérificateur utilisait l'inférence pour calculer les variables locales à chaque point de la méthode. Lorsque vous rencontrez un flux de contrôle, il fusionne dans les valeurs à ce point et itère jusqu'à la convergence. Malheureusement, cela a été lent, donc dans le but d'accélérer les choses, Oracle a ajouté des cartes de pile. Cela présélectionne essentiellement les résultats de la vérification à tout moment où le flux de contrôle est joint. De cette façon, le vérificateur peut faire un seul passage linéaire dans le code, car le flux de contrôle ne change pas les résultats. Lorsque le vérificateur rencontre un flux de contrôle, il vérifie si l'état actuel correspond à l'image de pile déclarée sur la cible de saut et, dans le cas contraire, renvoie une erreur. Dans les sections de code linéaire, il n'est évidemment pas nécessaire d'inclure des trames de pile, puisque le vérificateur peut faire exactement la même chose qu'avant.

Les cadres de pile ne sont pas conçus pour le débogage, ils sont destinés à accélérer la vérification, ils contiennent donc les informations minimales nécessaires à la vérification. Si le compilateur devait hypothétiquement insérer une trame de pile à chaque instruction, alors la trame de pile après le astore 4 montrerait bien sûr une nouvelle variable dans le 4ème emplacement. Pour ce qui est de savoir pourquoi il a utilisé l'emplacement 4 alors qu'il aurait pu utiliser l'emplacement 3, c'est juste un caprice du compilateur. Cela a peut-être simplifié la mise en œuvre de javac, mais ce n'est que de la spéculation.

+0

Ce dernier paragraphe est ce que je recherchais.Pourquoi ils ont choisi le slot 4 au lieu de 3. Merci. –