2014-07-24 12 views
15

Ich versuche, eine Unit-Tests für eine Problemumgehung zu an issue about missing stackmap frames zu schreiben, aber für diesen Zweck muss ich eine Klasse generieren, die auf Java 8 überprüfen kann, wenn es Stackmap Frames fehlt.Welche Art von Java-Code benötigt Stackmap-Frames?

Unten sehen Sie meinen Testfall (Abhängigkeiten: ASM, Guava, JUnit). Es entfernt die Stackmap-Frames aus der GuineaPig-Klasse in der Hoffnung, dass sein Bytecode nicht validiert werden kann. Der Teil, mit dem ich Probleme habe, füllt das TODO in GuineaPig mit minimalem Code aus, der Stackmap-Frames erfordert, damit der Test bestanden wird.

import com.google.common.io.*; 
import org.junit.*; 
import org.junit.rules.ExpectedException; 
import org.objectweb.asm.*; 

import java.io.*; 

import static org.objectweb.asm.Opcodes.ASM5; 

public class Java6MissingStackMapFrameFixerTest { 

    @Rule 
    public final ExpectedException thrown = ExpectedException.none(); 

    public static class GuineaPig { 
     public GuineaPig() { 
      // TODO: make me require stackmap frames 
     } 
    } 

    @Test 
    public void example_class_cannot_be_loaded_because_of_missing_stackmap_frame() throws Exception { 
     byte[] originalBytecode = getBytecode(GuineaPig.class); 

     ClassWriter cw = new ClassWriter(0); 
     ClassVisitor cv = new ClassVisitor(ASM5, cw) { 
      @Override 
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 
       return new MethodVisitor(ASM5, super.visitMethod(access, name, desc, signature, exceptions)) { 
        @Override 
        public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { 
         // remove the stackmap frames in order to cause a VerifyError 
//      super.visitFrame(type, nLocal, local, nStack, stack); 
        } 

       }; 
      } 
     }; 
     new ClassReader(originalBytecode).accept(cv, 0); 

     byte[] transformedBytecode = cw.toByteArray(); 
//  Files.asByteSink(new File("test.class")).write(transformedBytecode); 

     thrown.expect(VerifyError.class); 
     thrown.expectMessage("Expecting a stackmap frame"); 
     Class<?> clazz = new TestingClassLoader().defineClass(transformedBytecode); 
     clazz.newInstance(); 
    } 

    private static byte[] getBytecode(Class<?> clazz) throws IOException { 
     String classFile = clazz.getName().replace(".", "/") + ".class"; 
     try (InputStream b = clazz.getClassLoader().getResourceAsStream(classFile)) { 
      return ByteStreams.toByteArray(b); 
     } 
    } 

    private static class TestingClassLoader extends ClassLoader { 

     public Class<?> defineClass(byte[] bytecode) { 
      ClassReader cr = new ClassReader(bytecode); 
      String className = cr.getClassName().replace("/", "."); 
      return this.defineClass(className, bytecode, 0, bytecode.length); 
     } 
    } 
} 

Antwort

11

Theorie

Die Java-VM-Spezifikation §4.10.1 (Überprüfung durch Typprüfung) gibt an, dass, wenn ein Stapel Kartenrahmen erforderlich ist. Zunächst gibt es eine informelle Beschreibung:

Die Absicht ist, dass ein Stapel Map Frame am Anfang jedes Basisblocks in einer Methode angezeigt werden muss. Der Stapelkartenrahmen spezifiziert den Verifikationstyp jedes Operandenstapeleintrags und jeder lokalen Variablen am Anfang jedes Basisblocks.

Eine detaillierte Spezifikation ist in §4.10.1.6 (Typ Prüfmethoden mit Code) gegeben. Kartenrahmen Stapel werden durch den goto Befehl erforderlich:

Es ist illegal Code haben nach einer unbedingten Verzweigung ohne einen Stapel Kartenrahmen für sie vorgesehen ist.

und alle anderen Verzweigungsbefehle:

zu einer Zielverzweigungs sicher geben, wenn das Ziel einen zugehörigen Stapelrahmen, Rahmen aufweist und der aktuelle Stapelrahmen, Stackframe ist zuordenbar Rahmen.

auch den Beginn eines Ausnahmebehandler benötigt einen Stapel Kartenrahmen:

Eine Anweisung erfüllt eine Ausnahmebehandler, wenn ein abgehender Typ Zustand Anweisungen ist ExcStackFrame, und das Ziel der Handler (die anfängliche Instruktion des

Schließlich wird ein ankommender Typ Zustand unter der Annahme, T., §4.10.1.9 (Typ Checking Instructions) gibt an, dass die Befehle erfordern ein Verzweigungsziel mit einem Stapel Kartenrahmen-Handler-Code) ist sicher ein. Suchen Sie in den Typregeln nach targetIsTypeSafe; die Anweisungen goto, if*, lookupswitch und haben es.

Beispiel

Auch der folgende Code stackmap Rahmen erfordert:

public static class GuineaPig { 
    public GuineaPig() { 
     int i = 1; 
     if (i > 0) { 
      // code branch to require stackmap frames 
     } 
    } 
} 

Wenn sie fehlen, wird der Code mit einer Ausnahme fehlschlagen:

java.lang.VerifyError: Expecting a stackmap frame at branch target 10 
Exception Details: 
    Location: 
    net/orfjackal/retrolambda/Java6MissingStackMapFrameFixerTest$GuineaPig.<init>()V @7: ifle 
    Reason: 
    Expected stackmap frame at this location. 
    Bytecode: 
    0000000: 2ab7 000c 043c 1b9e 0003 b1    

     at java.lang.Class.getDeclaredConstructors0(Native Method) 
     at java.lang.Class.privateGetDeclaredConstructors(Class.java:2658) 
     at java.lang.Class.getConstructor0(Class.java:2964) 
     at java.lang.Class.newInstance(Class.java:403)   

ist die Bytecode:

public net.orfjackal.retrolambda.Java6MissingStackMapFrameFixerTest$GuineaPig(); 
    descriptor:()V 
    flags: ACC_PUBLIC 
    Code: 
     stack=1, locals=2, args_size=1 
     0: aload_0 
     1: invokespecial #1     // Method java/lang/Object."<init>":()V 
     4: iconst_1 
     5: istore_1 
     6: iload_1 
     7: ifle   10 
     10: return 
     LineNumberTable: 
     line 22: 0 
     line 23: 4 
     line 24: 6 
     line 27: 10 
     LocalVariableTable: 
     Start Length Slot Name Signature 
      0  11  0 this Lnet/orfjackal/retrolambda/Java6MissingStackMapFrameFixerTest$GuineaPig; 
      6  5  1  i I 
     StackMapTable: number_of_entries = 1 
      frame_type = 255 /* full_frame */ 
      offset_delta = 10 
      locals = [ class net/orfjackal/retrolambda/Java6MissingStackMapFrameFixerTest$GuineaPig, int ] 
      stack = [] 

P.S. Es hat einige Zeit gedauert, um das herauszufinden, weil ich meine Unit-Tests standardmäßig mit Code-Coverage durchführe und das Code Coverage Tool von IDEA anscheinend die Stackmap-Frames für alle Klassen automatisch neu berechnet, was die Bemühungen meines Tests, sie zu entfernen, rückgängig gemacht hat.