2014-07-24 5 views
15

मैं an issue about missing stackmap frames पर वर्कअराउंड के लिए यूनिट परीक्षण लिखने की कोशिश कर रहा हूं, लेकिन उस उद्देश्य के लिए मुझे एक कक्षा उत्पन्न करने की आवश्यकता होगी जो जावा 8 पर मान्य होने में विफल हो जाएगी यदि इसमें स्टैकमैप फ्रेम गुम हैं।किस प्रकार के जावा कोड को स्टैकमैप फ्रेम की आवश्यकता है?

नीचे आप मेरा टेस्ट केस (निर्भरता: एएसएम, अमरूद, जुनीट) देख सकते हैं। यह गिनीपीग कक्षा से स्टैकमैप फ्रेम को हटा देता है ताकि इसके बाइटकोड को मान्य करने में असफल हो सके। जिस हिस्से में मुझे समस्या है, वह गिनीपिग में TODO को न्यूनतम कोड के साथ भर रहा है जिसके लिए स्टैकमैप फ्रेम की आवश्यकता होती है, ताकि परीक्षण पास हो जाए।

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); 
     } 
    } 
} 

उत्तर

11

थ्योरी

जावा वी एम विशिष्टता §4.10.1 (सत्यापन प्रकार की जाँच करके) यह बताता है कि जब एक ढेर नक्शा फ्रेम की आवश्यकता है। सबसे पहले यह एक अनौपचारिक वर्णन देता है:

इरादा यह है कि एक विधि में प्रत्येक मूल ब्लॉक की शुरुआत में एक स्टैक मानचित्र फ्रेम दिखाई देना चाहिए। स्टैक मैप फ्रेम प्रत्येक मूल ब्लॉक की शुरुआत में प्रत्येक ऑपरेंड स्टैक एंट्री और प्रत्येक स्थानीय चर के सत्यापन प्रकार को निर्दिष्ट करता है।

§4.10.1.6 (कोड के साथ चेकिंग विधि टाइप करें) में एक विस्तृत विनिर्देश दिया गया है। ढेर नक्शा फ्रेम goto आदेश के लिए आवश्यक हैं:

यह इसके लिए प्रदान की जा रही एक ढेर नक्शा फ्रेम के बिना बिना शर्त शाखा के बाद कोड के लिए गैर कानूनी है।

और अन्य सभी शाखाओं में कमांड:

लक्ष्य को शाखाओं में बंटी, सुरक्षित टाइप करता है, तो लक्ष्य एक संबद्ध ढेर फ्रेम, फ़्रेम, और वर्तमान ढेर फ्रेम, StackFrame है फ्रेम आबंटित है।

इसके अलावा एक अपवाद संचालक की शुरुआत एक ढेर नक्शा फ्रेम की जरूरत है:

एक अनुदेश अपवाद संचालक अगर निर्देश के आउटगोइंग प्रकार राज्य ExcStackFrame है संतुष्ट करता है, और हैंडलर का लक्ष्य (की प्रारंभिक शिक्षा हैंडलर कोड) मानते हुए एक इनकमिंग प्रकार राज्य टी

अंत में, §4.10.1.9 (टाइप जाँच हो रही है निर्देश) है कि जो निर्देश एक ढेर नक्शा फ्रेम के साथ एक शाखाओं में लक्ष्य की आवश्यकता को निर्दिष्ट सुरक्षित टाइप है। टाइप नियमों में targetIsTypeSafe देखें; निर्देश goto, if*, lookupswitch और tableswitch है।

उदाहरण

यहां तक ​​कि निम्न कोड stackmap फ्रेम की आवश्यकता है:

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

वे याद कर रहे हैं, तो कोड एक अपवाद के साथ असफल हो जायेगी:

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)   

यहाँ बाईटकोड है:

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 = [] 

पीएस मुझे इसे समझने में कुछ समय लगा, क्योंकि डिफ़ॉल्ट रूप से मैं कोड कवरेज के साथ अपने यूनिट परीक्षण चलाता हूं और आईडीईए का कोड कवरेज टूल स्पष्ट रूप से स्वचालित रूप से सभी वर्गों के लिए स्टैकमैप फ्रेम का पुनर्मूल्यांकन करता है, जो उन्हें हटाने के लिए मेरे परीक्षण के प्रयासों को कम करता है।

संबंधित मुद्दे