代码之家  ›  专栏  ›  技术社区  ›  m49216

Java语言lang.VerifyError:(类:GregorSamsa,方法:…)跳转或分支的非法目标

  •  2
  • m49216  · 技术社区  · 7 年前

    在使用相当复杂的XSLT文档(2000个条件)创建大量xml转换器(javax.xml.transform.Transformer)时,我面临一个验证错误。请参见示例:

        public class XsltVerifyErrorTest {
    
        private static final int MAX_ITERATIONS_COUNT = 1000000;
    
        public static void main(String[] args) throws Exception {
            final byte[] xslBytes = Files.readAllBytes(new File(args[0]).toPath());
    
            for (int i = 0; i < MAX_ITERATIONS_COUNT; i++) {
                System.out.println(String.format("Iteration %d", i));
    
                final StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes));
                final Transformer transformer = TransformerFactory.newInstance().newTransformer(xslSource);
            }
        }
    }
    
    
    
    m49216@ubuntu:~/xslt-verify-error-test$ javac XsltVerifyErrorTest.java
    m49216@ubuntu:~/xslt-verify-error-test$ java -showversion XsltVerifyErrorTest XsltVerifyErrorTest.xsl
    openjdk version "1.8.0_162"
    OpenJDK Runtime Environment (build 1.8.0_162-8u162-b12-0ubuntu0.16.04.2-b12)
    OpenJDK 64-Bit Server VM (build 25.162-b12, mixed mode)
    
    Iteration 0
    Iteration 1
    Iteration 2
    ...
    Iteration 79
    Iteration 80
    Iteration 81
    Exception in thread "main" java.lang.VerifyError: (class: GregorSamsa, method: TestTemplate signature: (Lcom/sun/org/apache/xalan/internal/xsltc/DOM;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;ILjava/lang/Object;)V) Illegal target of jump or branch
            at java.lang.Class.getDeclaredConstructors0(Native Method)
            at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
            at java.lang.Class.getConstructor0(Class.java:3075)
            at java.lang.Class.newInstance(Class.java:412)
            at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455)
            at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
            at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTransformer(TransformerFactoryImpl.java:762)
            at XsltVerifyErrorTest.main(XsltVerifyErrorTest.java:25)
    

    可以找到带有测试数据的完整示例 here .

    这个问题可以在许多版本的jdk上重现:8u101、8u121、8u152、8u161、8u162。

    有人遇到过这个问题吗?它看起来像jvm bug吗?

    编辑1: JDK 9也受到了影响,但要再现这个问题要困难得多——在我的机器上需要大约20分钟和660次迭代。

    java version "9.0.4"
    Java(TM) SE Runtime Environment (build 9.0.4+11)
    Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)
    Iteration 0
    Iteration 1
    Iteration 2
    ...
    Iteration 658
    Iteration 659
    Iteration 660
    Exception in thread "main" java.lang.VerifyError: (class: die/verwandlung/GregorSamsa, method: TestTemplate signature: (Lcom/sun/org/apache/xalan/internal/xsltc/DOM;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;ILjava/lang/Object;)V) Illegal target of jump or branch
        at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
        at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3110)
        at java.base/java.lang.Class.getConstructor0(Class.java:3315)
        at java.base/java.lang.Class.newInstance(Class.java:530)
        at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:552)
        at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:583)
        at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTransformer(TransformerFactoryImpl.java:817)
        at XsltVerifyErrorTest.main(XsltVerifyErrorTest.java:19)
    

    编辑2: 在JDK 10上运行良好。

    1 回复  |  直到 7 年前
        1
  •  3
  •   apangin    7 年前

    我有机会更深入地研究这个问题,最终解决了这个难题。
    这确实是JDK的BCEL库中的一个bug。

    TransformerFactory 基于源XSLT生成字节码。字节码生成步骤之一是 remove temporary NOPs 并重新定位目标(指向它们的分支指令)。

    目标保持在 HashSet . 这就是问题所在。 Instruction 类未定义 hashCode ,及其 equals 方法对分支的作用完全错误:如果两条分支指令的目标相同,则认为它们相等。当然,这不是真的。

    但只要分支哈希代码不同,问题就不会发生。因为这些实际上是默认的身份哈希码,所以它们发生冲突的可能性很小。但在多次迭代之后,由于生成的方法非常大,最终会发生这种情况:具有相同目标的两个不同分支指令获得相同的标识哈希代码,并且由于冲突,其中一个会覆盖另一个。

    使用运行程序 -XX:hashCode=2 (这将强制使用退化的标识哈希码),它将立即崩溃。

    错误已在中修复 BCEL-195 并集成到JDK 10中 JDK-8163121 .

    早期的JDK版本仍然存在此缺陷,但幸运的是,您可以使用以下解决方法。打个电话就行了 Instruction.setComparator 在应用程序启动时:

        import com.sun.org.apache.bcel.internal.generic.*;
        ...
    
        Instruction.setComparator((i1, i2) -> {
            if (i1 instanceof BranchInstruction) {
                return i1 == i2;
            }
            return InstructionComparator.DEFAULT.equals(i1, i2);
        });