Skip to content

[GR-76332] Preserve reflection linkage errors.#13738

Open
graalvmbot wants to merge 3 commits into
masterfrom
vj/GR-68156-fix-warnings
Open

[GR-76332] Preserve reflection linkage errors.#13738
graalvmbot wants to merge 3 commits into
masterfrom
vj/GR-68156-fix-warnings

Conversation

@graalvmbot

Copy link
Copy Markdown
Collaborator

Summary:
Native Image now preserves JVM reflection lookup failures when registered reflection metadata refers to missing classes. The image build records the query-level LinkageError that core reflection would produce, then rethrows it from the generated image instead of returning incomplete metadata or failing while encoding metadata.

What was failing before:
A user can compile an application while a signature type is present, then run or build it in an environment where that signature type is missing. Core reflection resolves some signature types lazily, so the class itself can still load, but a metadata query fails when it tries to expose the missing type.

For example, each of these JVM reflection queries can fail independently:

BrokenMethods.class.getDeclaredMethods();
BrokenFields.class.getDeclaredFields();
BrokenConstructors.class.getDeclaredConstructors();
BrokenRecord.class.getRecordComponents();

Before this change, Native Image could diverge from the JVM. Some generated images returned successful metadata where the JVM would throw LinkageError. The record-component variant also exposed a hosted encoding problem: the build tried to call Class#getRecordComponents() while encoding metadata, so it failed with NoClassDefFoundError before it could preserve the lookup error for run time.

Reproducer:
Create this source file:

// Reproducer.java
public class Reproducer {
    public static void main(String[] args) {
        expectLinkageError("getDeclaredMethods", () -> BrokenMethods.class.getDeclaredMethods());
        expectLinkageError("getDeclaredFields", () -> BrokenFields.class.getDeclaredFields());
        expectLinkageError("getDeclaredConstructors", () -> BrokenConstructors.class.getDeclaredConstructors());
        expectLinkageError("getRecordComponents", () -> BrokenRecord.class.getRecordComponents());
    }

    private static void expectLinkageError(String query, Runnable action) {
        try {
            action.run();
            throw new AssertionError(query + " unexpectedly succeeded");
        } catch (LinkageError expected) {
            System.out.println(query + " failed as expected: " + expected.getClass().getName());
        }
    }
}

class MissingType {
}

class BrokenMethods {
    MissingType method(MissingType value) {
        return value;
    }
}

class BrokenFields {
    MissingType field;
}

class BrokenConstructors {
    BrokenConstructors(MissingType value) {
    }
}

record BrokenRecord(MissingType component) {
}

Compile it, then remove only the referenced signature type:

javac Reproducer.java
rm MissingType.class

The JVM behavior is that the individual reflection queries throw LinkageError:

java Reproducer

Register the same metadata for Native Image:

[
  { "name": "BrokenMethods", "allDeclaredMethods": true },
  { "name": "BrokenFields", "allDeclaredFields": true },
  { "name": "BrokenConstructors", "allDeclaredConstructors": true },
  { "name": "BrokenRecord", "allRecordComponents": true }
]

Build and run the image:

native-image --no-fallback -H:ReflectionConfigurationFiles=reflect-config.json Reproducer
./reproducer

Expected behavior:
The generated image should match the JVM: each affected query rethrows LinkageError. It should not silently return partial metadata, and it should not fail during image generation while trying to materialize record components whose component type is missing.

Key architectural changes:
ReflectionDataBuilder now treats reflection lookup failures as part of the metadata contract. When it probes core reflection behavior during analysis, it stores query-specific failures on TypeData:

fieldLookupLinkageError
methodLookupLinkageError
constructorLookupLinkageError
recordComponentLookupLinkageError

RuntimeMetadataEncoderImpl then consumes these through ReflectionHostedSupport alongside the materialized metadata:

getFieldLookupErrors()
getMethodLookupErrors()
getConstructorLookupErrors()
getRecordComponentLookupErrors()

For record components, ReflectionHostedSupport#getRecordComponents now has an explicit split in meaning:

RecordComponent[] getRecordComponents(Class<?> type);
Map<Class<?>, Throwable> getRecordComponentLookupErrors();

getRecordComponents returns only metadata that can be safely materialized during image building. If Class#getRecordComponents() is supposed to fail for a type, the encoder must read getRecordComponentLookupErrors() instead. This prevents hosted metadata materialization from bypassing the preserved-error path.

Why multiple fields are needed:
The JVM APIs fail independently. A class can have a broken field signature while its methods and constructors are still queryable:

try {
    BrokenFields.class.getDeclaredFields();
} catch (LinkageError expected) {
    // Only the field query is broken.
}

BrokenFields.class.getDeclaredMethods();      // should still work if method signatures are valid
BrokenFields.class.getDeclaredConstructors(); // should still work if constructor signatures are valid

A single LinkageError field on TypeData would be wrong in one of two ways:

// Over-approximation: one missing field type would also poison method queries.
allMemberLookupError = fieldError;

// Under-approximation: storing only one error would lose a different query failure.
allMemberLookupError = methodError; // fieldError is gone

The separate fields preserve the behavior of each Class API entry point exactly. They also keep partial metadata usable when only one lookup family is broken, which matches the JVM and avoids making unrelated reflection queries fail in the generated image.

@oracle-contributor-agreement oracle-contributor-agreement Bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Jun 9, 2026
@graalvmbot graalvmbot force-pushed the vj/GR-68156-fix-warnings branch 3 times, most recently from 9bc9577 to b01854e Compare June 10, 2026 16:29
@graalvmbot graalvmbot changed the title [GR-76332] Preserve reflection linkage errors [GR-76332] Preserve reflection linkage errors. Jun 11, 2026
@graalvmbot graalvmbot force-pushed the vj/GR-68156-fix-warnings branch from b01854e to e61e114 Compare June 11, 2026 08:40
@graalvmbot graalvmbot force-pushed the vj/GR-68156-fix-warnings branch from e61e114 to e51a954 Compare June 18, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OCA Verified All contributors have signed the Oracle Contributor Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants