oracle/graal

`*.isAssignableFrom(clazz)` does not deliver correct result in native-image

cmdjulian opened this issue · 3 comments

Describe the issue
I encountered a really strange issue today. I have a small app which is compiled to native-image using the fabric8 Kubernetes client.
The client contains the following I shortened to make it more clear:

enum SupportedResponses {
  TEXT(String.class), INPUT_STREAM(InputStream.class), READER(Reader.class), BYTE_ARRAY(byte[].class);

  private final Class<?> type;

  <T> SupportedResponses(Class<T> type) {
    this.type = type;
  }

  public static SupportedResponses from(Class<?> type) {
    for (SupportedResponses sr : SupportedResponses.values()) {
      if (sr.type.isAssignableFrom(type)) {
        return sr;
      }
    }
    throw new IllegalArgumentException("Unsupported response type: " + type.getName());
  }
}

Eventually the from method is called: HttpResponse.SupportedResponses.from(byte[].class). This works without a problem in Java, but does not work in native-image. In native-image throw new IllegalArgumentException("Unsupported response type: " + type.getName()); is reached everytime. As you can conduct from the code, this should not happen with byte[].class.

When setting the optimization level from -O2 to -O0 the error is gone.

The issue appeared after updating to OpenJDK 64-Bit Server VM GraalVM CE 21+35.1. Before I used GraalVM CE 17.0.7+7.1 where the bug seem to not be present.

Even more interesting to me, when I substitute the code of the lib with the following semantically equivalent:

@TargetClass(HttpResponse.SupportedResponses.class)
public final class Target_io_fabric8_kubernetes_client_http_HttpResponse_SupportedResponses {
    @Substitute
    public static HttpResponse.SupportedResponses from(Class<?> type) {
        if (type.isAssignableFrom(String.class)) {
            return HttpResponse.SupportedResponses.TEXT;
        } else if (type.isAssignableFrom(InputStream.class)) {
            return HttpResponse.SupportedResponses.INPUT_STREAM;
        } else if (type.isAssignableFrom(Reader.class)) {
            return HttpResponse.SupportedResponses.READER;
        } else if (type.isAssignableFrom(byte[].class)) {
            return HttpResponse.SupportedResponses.BYTE_ARRAY;
        } else {
            throw new IllegalArgumentException("Unsupported response type: " + type.getName());
        }
    }
}

the error is gone, even for -O3 optimization level. When checking inside of my substituded function for isAssigbaleFrom() myself with the values from the enum, not the class literals, it returns true as expected. It may has something to do with the for loop my substitution is lacking.

I created a little project which employees TestContainers and a small test which succeeds in JVM mode and fails with the aforementioned error in native test mode.

Steps to reproduce the issue
Please include both build steps as well as run steps.
As the test employs TestContainers, make sure a working Docker environment is setup and usable by TestContainers

  1. git clone 'https://github.com/cmdjulian/graal-is-assigable-buggy.git'
  2. ./gradlew test <-- succeeds
  3. ./gradlew nativeTest <-- fails

Describe GraalVM and your environment:

  • GraalVM version (latest snapshot builds can be found here), or commit id if built from source: GraalVM CE 21+35.1 (build 21+35-jvmci-23.1-b15)
  • JDK major version: 21
  • OS: Linux
  • Architecture: AMD64

More details

Happens for Oracle GraalVM Java(TM) SE Runtime Environment Oracle GraalVM 21+35.1 (build 21+35-jvmci-23.1-b15) as well.

image