Class SafeExceptionTableMethodVisitor

java.lang.Object
org.objectweb.asm.MethodVisitor
org.ek9lang.compiler.backend.jvm.SafeExceptionTableMethodVisitor

final class SafeExceptionTableMethodVisitor extends org.objectweb.asm.MethodVisitor
THE single defence-in-depth chokepoint that makes a zero-length JVM exception-table entry impossible to emit.

A guarded region whose start and end labels resolve to the SAME bytecode offset (start_pc == end_pc) is a degenerate exception-table entry that the JVM verifier rejects at class-load with ClassFormatError: Illegal exception table range — the program compiles clean and only fails when it is run. (See BUG-try-expression-with-resource-invalid-bytecode.md for the phase-7 root cause that used to produce one.) A zero-length region contains no instruction, so it can catch nothing and its handler is unreachable; dropping the entry is therefore behaviour-neutral AND turns would-be invalid bytecode into a verifiable class.

This wrapper BUFFERS every visitTryCatchBlock call and FLUSHES them, in registration order, at the top of visitMaxs (delegating to the real visitTryCatchBlock for each, then to super.visitMaxs). Deferral is necessary — a static per-call-site start==end check would not work — because a caller may register the exception block BEFORE placing its labels (javac-style forward declaration), where the labels' offsets are not yet resolved. By visitMaxs every visited label IS resolved (visitLabel precedes visitMaxs in the ASM contract), so Label.getOffset() is valid and the degenerate entries can be dropped. Flushing in FIFO order preserves the exception-table order (first-registered = first-match), which try/catch/finally handler priority relies on.

Installed once, in Ek9ClassWriter.toByteArray() — ASM's ClassWriter.visitMethod is final, so the chokepoint is applied as a ClassReader -> ClassWriter filter over the finished class instead (each method is copied through this visitor). The completeness of the chokepoint depends on the invariant that every backend ClassWriter is an Ek9ClassWriter (never a raw org.objectweb.asm.ClassWriter), so its filtering toByteArray runs — otherwise it is bypassed. That invariant is guarded by Ek9ClassWriterUsageInvariantTest.

  • Field Summary

    Fields inherited from class org.objectweb.asm.MethodVisitor

    api, mv
  • Constructor Summary

    Constructors
    Constructor
    Description
    SafeExceptionTableMethodVisitor(org.objectweb.asm.MethodVisitor delegate)
     
  • Method Summary

    Modifier and Type
    Method
    Description
    (package private) static byte[]
    Copy classBytes verbatim (frames/maxs preserved — no recomputation, so no class loading is needed) except that every method is filtered through SafeExceptionTableMethodVisitor, dropping any exception-table entry whose start and end labels resolve to the same offset.
    void
    visitMaxs(int maxStack, int maxLocals)
     
    void
    visitTryCatchBlock(org.objectweb.asm.Label start, org.objectweb.asm.Label end, org.objectweb.asm.Label handler, String type)
     

    Methods inherited from class org.objectweb.asm.MethodVisitor

    getDelegate, visitAnnotableParameterCount, visitAnnotation, visitAnnotationDefault, visitAttribute, visitCode, visitEnd, visitFieldInsn, visitFrame, visitIincInsn, visitInsn, visitInsnAnnotation, visitIntInsn, visitInvokeDynamicInsn, visitJumpInsn, visitLabel, visitLdcInsn, visitLineNumber, visitLocalVariable, visitLocalVariableAnnotation, visitLookupSwitchInsn, visitMethodInsn, visitMethodInsn, visitMultiANewArrayInsn, visitParameter, visitParameterAnnotation, visitTableSwitchInsn, visitTryCatchAnnotation, visitTypeAnnotation, visitTypeInsn, visitVarInsn

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • SafeExceptionTableMethodVisitor

      SafeExceptionTableMethodVisitor(org.objectweb.asm.MethodVisitor delegate)
  • Method Details

    • filterDegenerateExceptionTables

      static byte[] filterDegenerateExceptionTables(byte[] classBytes)
      Copy classBytes verbatim (frames/maxs preserved — no recomputation, so no class loading is needed) except that every method is filtered through SafeExceptionTableMethodVisitor, dropping any exception-table entry whose start and end labels resolve to the same offset. In the serialized class those offsets are always resolved. For valid code (no degenerate range) this is a faithful, normalisation-equivalent copy — the filter only ever removes would-be-invalid entries. This is THE chokepoint; see the class javadoc.
    • visitTryCatchBlock

      public void visitTryCatchBlock(org.objectweb.asm.Label start, org.objectweb.asm.Label end, org.objectweb.asm.Label handler, String type)
      Overrides:
      visitTryCatchBlock in class org.objectweb.asm.MethodVisitor
    • visitMaxs

      public void visitMaxs(int maxStack, int maxLocals)
      Overrides:
      visitMaxs in class org.objectweb.asm.MethodVisitor