javajavacjava-21java-24java-25

Why does using instanceof pattern matching on a record field with a switch expression in the same method cause a java.lang.VerifyError?


I found this minimal set of code which seems to break any Java version I can find. (Also reproducible in https://www.onlinegdb.com/online_java_compiler with Java 23.0.2+7-58). The example is not meant to do anything useful except to demonstrate the issue — my actual code is more complex.

record VehicleInput(Vehicle vehicle) {}

sealed interface Vehicle permits Car {}

record Car(String licensePlate) implements Vehicle {}

sealed interface Animal permits Cat, Dog {}

record Cat() implements Animal {}

record Dog() implements Animal {}

class VehicleProgressor {
  private boolean progress(VehicleInput input, Animal animal) {
    if (input.vehicle() instanceof Car(String licensePlate)) {
      return true;
    }

    return switch (animal) {
      case Cat cat -> true;
      case Dog dog -> true;
    };
  }
}

public class Main {
  public static void main(String[] args) {
    System.out.println(Runtime.version());

    new VehicleProgressor();
  }
}

(Versions managed with sdkman)

$ sdk install java 24.0.1-amzn
$ sdk use java 24.0.1-amzn

(The error output is exactly the same with java 21.0.7-amzn, java 24.0.1-amzn, java 25.ea.25-open, and java 25.ea.25-graal.)

Compiling works:

$ javac Main.java

but running fails with:

$ java Main
24.0.1+9-FR
Exception in thread "main" java.lang.VerifyError: Inconsistent stackmap frames at branch target 96
Exception Details:
  Location:
    VehicleProgressor.progress(LVehicleInput;LAnimal;)Z @96: aload_3
  Reason:
    Type top (current frame, locals[5]) is not assignable to 'Cat' (stack map, locals[5])
  Current Frame:
    bci: @50
    flags: { }
    locals: { 'VehicleProgressor', 'VehicleInput', 'Animal', 'Animal', integer }
    stack: { integer }
  Stackmap Frame:
    bci: @96
    flags: { }
    locals: { 'VehicleProgressor', 'VehicleInput', 'Animal', 'Animal', integer, 'Cat' }
    stack: { }
  Bytecode:
    0000000: 2bb6 0007 3a05 1905 c100 0d99 0015 1905
    0000010: c000 0d4e 2db6 000f 3a06 1906 3a04 04ac
    0000020: 2c59 b800 1357 4e03 3604 2d15 04ba 0019
    0000030: 0000 ab00 0000 001a 0000 0002 0000 0000
    0000040: 0000 0024 0000 0001 0000 002e bb00 1d59
    0000050: 0101 b700 1fbf 2dc0 0022 3a05 04a7 000a
    0000060: 2dc0 0024 3a06 04ac 4ebb 001d 592d b600
    0000070: 282d b700 1fbf
  Exception Handler Table:
    bci [21, 24] => handler: 104
  Stackmap Table:
    same_frame(@32)
    append_frame(@42,Object[#50],Integer)
    same_frame(@76)
    same_frame(@86)
    append_frame(@96,Object[#34])
    full_frame(@103,{Object[#43],Object[#8],Object[#50]},{Integer})
    same_locals_1_stack_item_frame(@104,Object[#38])

    at Main.main(Main.java:30)

Is this a bug in Java/JDK?

If I change the class in the above code into:

class VehicleProgressor {
  private boolean progress(VehicleInput input, Animal animal) {
    if (input.vehicle() instanceof Car) {
      return true;
    }

    return switch (animal) {
      case Cat cat -> true;
      case Dog dog -> true;
    };
  }
}

or into:

class VehicleProgressor {
  private boolean progress(VehicleInput input, Animal animal) {
    if (input.vehicle() instanceof Car(String licensePlate)) {
      return true;
    }
    return true;
  }
}

or into:

class VehicleProgressor {
  private boolean progress(Vehicle vehicle, Animal animal) {
    if (vehicle instanceof Car(String licensePlate)) {
      return true;
    }

    return switch (animal) {
      case Cat cat -> true;
      case Dog dog -> true;
    };
  }
}

it runs fine just printing the version number.

The super strange part is that I'm not even calling the progress method anywhere. I also tried having everything in separate files as public interfaces/records, but that made no difference.


Solution

  • Turned out to be a clear bug. Reported based on @Jorn Vernee's suggestion to compiler-dev@openjdk.org.

    Based on the commenters, seems to only be an issue with javac and not with Eclipse Compiler for Java (ECJ).

    Update: The bug could be reproduced with an even more minimal example; PR for the bugfix with additional details: https://github.com/openjdk/jdk/pull/25849

    Targeted to be included in version 26: https://bugs.openjdk.org/browse/JDK-8358801