Skip to content

Expose Dynamic Access Task #870

Description

@vjovanov

Summary

Add a new repo-driven analysis output to Native Build Tools that emits, for the current resolved classpath, only the methods whose parameters participate in dynamic-access calls.

The task/goal reads precomputed method-level JSON from the configured reachability metadata repository, resolves the effective metadata version for each classpath artifact using the same repository-selection rules already used for reachability metadata, filters to the current project classpath, and writes a standalone JSON artifact.

The method-level JSON in the repository is produced by agent-based static analysis of library source code in the metadata repository pipeline. Native Build Tools consumes this pre-validated data without re-analyzing bytecode.

Native Build Tools performs local agent-based analysis of user source code to:

  1. Correlate call sites where user code invokes exposed third-pary library methods that perform dynamic access
  2. Detect direct dynamic access calls in user code itself and expose those to the third-party libs

This provides a complete picture: library exposures from the repository + user code exposures and call sites from local analysis.

Workflow Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                                  INPUTS                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────┐      ┌─────────────────────────────────────┐   │
│  │   Project Classpath     │      │      Metadata Repository            │   │
│  │                         │      │                                     │   │
│  │  • lib-a:1.2.3.jar      │      │  exposed-dynamic-access/            │   │
│  │  • lib-b:4.5.6.jar      │      │    └─ <group>/<artifact>/<version>/ │   │
│  │  • lib-c:7.8.9.jar      │      │         └─ exposed-dynamic-access.json│ │
│  └───────────┬─────────────┘      └──────────────────┬──────────────────┘   │
│              │                                       │                      │
└──────────────┼───────────────────────────────────────┼──────────────────────┘
               │                                       │
               ▼                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              PROCESSING                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. Resolve metadata version per artifact                                   │
│     (reuses existing reachability metadata version resolution)              │
│                                         │                                   │
│                                         ▼                                   │
│  2. Fetch exposed-dynamic-access.json for each artifact from repository     │
│     (skip artifacts without repo data)                                      │
│                                         │                                   │
│                                         ▼                                   │
│  3. Agent-based source analysis                                             │
│     • Correlate: find call sites in user code that invoke exposed methods   │
│     • Intrinsic: detect direct dynamic access calls in user code            │
│                                         │                                   │
│                                         ▼                                   │
│  4. Merge & aggregate by class, deterministic ordering                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                         │
                                         ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                                  OUTPUT                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  exposed-dynamic-access.json                                                │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │ {                                                                     │  │
│  │   "schemaVersion": "1.0.0",                                           │  │
│  │   "repositoryAvailable": true,                                        │  │
│  │   "inputs": [ { artifact, metadataVersion }, ... ],                   │  │
│  │   "classes": [                                                        │  │
│  │     { "className": "...", "origin": "repository|source_analysis",     │  │
│  │       "methods": [                                                    │  │
│  │         { "signature": "...", "parameters": [                         │  │
│  │           { "index": 0, "influences": [                               │  │
│  │             { dependencyKind, dynamicAccessType, trackedApi }         │  │
│  │           ]}                                                          │  │
│  │         ]}                                                            │  │
│  │     ]}                                                                │  │
│  │   ],                                                                  │  │
│  │   "callSites": [ ... ]                                                │  │
│  │ }                                                                     │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  Location:                                                                  │
│    Gradle: build/generated/<task-name>/exposed-dynamic-access.json          │
│    Maven:  target/exposed-dynamic-access.json                               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Key Changes

Public interfaces

  • Add Gradle per-binary tasks mirroring existing native-image task naming:
    • generateExposedDynamicAccess
    • Same pattern for any additional registered binaries
  • Add a Maven goal:
    • generateExposedDynamicAccess
  • Default outputs:
    • Gradle: build/generated/<task-name>/exposed-dynamic-access.json
    • Maven: target/exposed-dynamic-access.json

Output JSON

The task produces a single aggregated output for the entire classpath, with methods grouped by class and merged from all artifacts that have parameter influence data:

{
  "schemaVersion": "1.0.0",
  "repositoryAvailable": true,
  "inputs": [
    {
      "artifact": "com.example:library:1.2.3",
      "metadataVersion": "1.2.0"
    },
    {
      "artifact": "org.other:util:4.5.6",
      "metadataVersion": "4.5.0"
    }
  ],
  "classes": [
    {
      "className": "com.example.Library",
      "origin": "repository",
      "methods": [
        {
          "signature": "load(java.lang.String, boolean) -> java.lang.Class",
          "parameters": [
            {
              "index": 0,
              "influences": [
                {
                  "dependencyKind": "direct",
                  "dynamicAccessType": "reflection",
                  "trackedApi": "java.lang.Class#forName(java.lang.String)"
                },
                {
                  "dependencyKind": "derived",
                  "dynamicAccessType": "reflection",
                  "trackedApi": "java.lang.Class#getDeclaredMethod(java.lang.String,java.lang.Class[])"
                }
              ]
            },
            {
              "index": 1,
              "influences": [
                {
                  "dependencyKind": "control_flow",
                  "dynamicAccessType": "reflection",
                  "trackedApi": "java.lang.Class#forName(java.lang.String)"
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "callSites": []
}

Rules:

  • inputs lists only artifacts that contributed parameter influence data (had exposed-dynamic-access.json in repo).
  • inputs[*].artifact is groupId:artifactId:version from the classpath.
  • inputs[*].metadataVersion is the resolved metadata version used to read exposed-dynamic-access.json.
  • classes[*].origin is "repository" for library exposures from the metadata repository, or "source_analysis" for exposures detected in user code by local analysis.
  • callSites contains correlation data from source analysis (empty array when source analysis is disabled).
  • Only classes with at least one method having influenced parameters appear.
  • Only methods with at least one influenced parameter appear.
  • Parameter indices are zero-based.
  • dependencyKind is a closed set. See Dependency kind semantics below.
  • dynamicAccessType is pass-through from repo data; do not hard-code it beyond string transport.
  • If the metadata repository is unavailable or disabled, still write a valid file with repositoryAvailable: false, inputs: [], classes: [], and callSites: [].

Dependency kind semantics

  • dependencyKind must be one of direct, derived, or control_flow. Native Build Tools treats this as repo-authored classification data and must reject or ignore any unexpected value rather than inventing a fallback category.
  • direct means the parameter value itself is used as the dynamic-access input, or it flows to that input without any material transformation. Typical cases are passing a String parameter directly to Class.forName, using a parameter as the member name in reflective lookup, or forwarding the same value through simple aliases.
  • derived means the parameter still influences the dynamic-access input, but only after an intermediate transformation, lookup, extraction, or composition step. Typical cases are deriving a class name from a parameter object, reading a reflective target from a map keyed by the parameter, or concatenating a package prefix with the parameter before the reflective call.
  • control_flow means the parameter does not supply the dynamic-access operand itself, but it decides whether a dynamic-access call happens or which precomputed target is chosen. Typical cases are boolean flags, enums, or mode strings that select between branches where each branch performs different reflection/resource/proxy access.
  • Classification is per influence record, not per parameter globally. The same parameter may legitimately produce multiple influence entries if it affects different tracked APIs in different ways.
  • When a situation could fit more than one category, prefer the most direct explanation of how the parameter affects the tracked API input:
    • Use direct when the tracked API consumes the parameter value itself.
    • Use derived when the tracked API consumes a value computed from the parameter.
    • Use control_flow when the parameter only selects the path that eventually performs dynamic access.

Classification decision rule

The classification depends on data flow to the API operand, not on whether the parameter appears in control flow constructs. The key question is: does the parameter's VALUE flow to the tracked API, or does the parameter merely SELECT which predetermined value gets used?

Parameter appears in condition? API operand is... Classification
Yes or No Parameter reference (className) direct
Yes or No String literal ("com.example.Foo") control_flow
Yes or No Computed from param (name + ".Impl") derived

Examples

Example 1: Pure direct — no control flow

void process(String className) {
    Class.forName(className);
}

Classification: direct — the parameter IS the operand.

Example 2: Direct with validation guard

void process(String className) {
    if (isAllowedClass(className)) {
        Class.forName(className);  // ← parameter reference
    }
}

Classification: direct — despite the conditional guard, the parameter value itself flows to Class.forName. The control flow is validation/filtering, not target selection.

Example 3: Parameter in switch, passed directly in each case

void process(String type) {
    switch(type) {
        case "A": Class.forName(type); break;  // ← parameter reference
        case "B": Class.forName(type); break;  // ← parameter reference
    }
}

Classification: direct — the switch constrains possible values, but the parameter itself flows to the API. The operand is type, not a literal.

Example 4: Control flow selecting hardcoded targets

void process(String mode) {
    if (mode.equals("debug")) {
        Class.forName("com.example.DebugHandler");  // ← string literal
    } else {
        Class.forName("com.example.ReleaseHandler");  // ← string literal
    }
}

Classification: control_flow — the parameter selects the branch, but the reflection targets are hardcoded literals. Native Image can statically enumerate both targets.

Example 5: Condition values match literals (no data flow)

void process(String className) {
    if (className.equals("com.example.Foo")) {
        Class.forName("com.example.Foo");  // ← string literal, not parameter
    } else if (className.equals("com.example.Bar")) {
        Class.forName("com.example.Bar");  // ← string literal, not parameter
    }
}

Classification: control_flow — even though the literals coincidentally match the condition values, there is no data flow from parameter to operand. The operands are string literals. The parameter merely selects which literal gets used.

Example 6: Boolean/enum parameter

void process(boolean useDebug) {
    if (useDebug) {
        Class.forName("DebugImpl");
    } else {
        Class.forName("ProdImpl");
    }
}

Classification: control_flow — the parameter cannot possibly be the class name (wrong type). It purely selects which hardcoded target is used.

Example 7: Derived with control flow guard

void process(String module) {
    if (module.equals("core") || module.equals("ext")) {
        Class.forName("com." + module + ".Main");  // ← concatenation
    }
}

Classification: derived — the operand is computed from the parameter via string concatenation, not the parameter itself.

Example 8: Same parameter, multiple influence patterns

void process(String config) {
    if (config.equals("special")) {
        Class.forName("SpecialClass");    // ← hardcoded literal
    }
    Class.forName(config);                 // ← parameter reference
}

Classification: Two separate influence entries

  • First call → control_flow (parameter selects whether this literal-based call happens)
  • Second call → direct (parameter value flows to API)

Key distinction

The critical distinction is between:

  • Class.forName("Foo") — hardcoded literal → control_flow if parameter selects this branch
  • Class.forName(paramName) — parameter reference → direct even if parameter appears in control flow conditions

When a parameter appears in both a condition and as the API operand (e.g., if (x.equals("A")) Class.forName(x)), classify as direct because the operand is the parameter itself — not a literal that happens to match.

When a parameter appears in a condition but the API uses literals (e.g., if (x.equals("A")) Class.forName("A")), classify as control_flow because the operand is a literal — the parameter merely selects which literal executes.

Composition of dependency kinds

When a parameter's influence flows through multiple steps (e.g., parameter → intermediate value → tracked API), the combined dependency kind follows a join semilattice with total order:

direct < derived < control_flow

The composition rule is: take the maximum (least direct) of the two kinds.

Composition table:

First step Second step Combined result
direct direct direct
direct derived derived
direct control_flow control_flow
derived direct derived
derived derived derived
derived control_flow control_flow
control_flow direct control_flow
control_flow derived control_flow
control_flow control_flow control_flow

Lattice diagram:

    control_flow   ← top (least direct: parameter only selects path)
         │
      derived      ← middle (parameter transformed before reaching operand)
         │
       direct      ← bottom (most direct: parameter IS the operand)

Intuition:

  • direct means value identity is preserved through the entire chain
  • derived means value is transformed at some point, but still flows to the operand
  • control_flow means at some point the value stops flowing and instead selects a path; whatever happens downstream, the original parameter only controlled execution

Composition examples:

  1. direct ⊕ direct = direct

    String alias = param;       // direct: param → alias
    Class.forName(alias);       // direct: alias → API
    // Combined: param → API is direct
  2. direct ⊕ derived = derived

    String name = param;                  // direct: param → name
    Class.forName(name + "Impl");         // derived: name → API
    // Combined: param → API is derived
  3. derived ⊕ direct = derived

    String upper = param.toUpperCase();   // derived: param → upper
    Class.forName(upper);                 // direct: upper → API
    // Combined: param → API is derived
  4. direct ⊕ control_flow = control_flow

    String mode = param;                          // direct: param → mode
    if (mode.equals("A")) Class.forName("X");     // control_flow: mode → API
    // Combined: param → API is control_flow
  5. control_flow ⊕ direct = control_flow

    // param selects which value to use (control_flow)
    String target = param.equals("A") ? "ClassA" : "ClassB";
    Class.forName(target);                        // direct: target → API
    // Combined: param → API is control_flow (param selected, didn't flow)

Metadata repository contract

  • Define a new root-level repository data location, outside metadata/<group>/<artifact>/<version>/, so it is not copied into META-INF/native-image:
    • exposed-dynamic-access/<groupId>/<artifactId>/<metadataVersion>/exposed-dynamic-access.json
  • Do not store this file beside reachability-metadata.json; existing copy logic would treat it as runtime config.
  • Native Build Tools reuses the existing reachability metadata version resolution logic—no new resolution implementation needed. The same metadataVersion resolved for reachability metadata applies here.

Shared core behavior

  • Put the shared model, parser, merger, and deterministic serializer in shared common/ code so Gradle and Maven wrappers stay thin.
  • The shared core takes:
    • Repository root
    • Resolved classpath artifacts for the current build scope
    • Effective metadata version per artifact (obtained from existing reachability metadata resolution)
  • The shared core returns methods grouped by class, aggregated from all artifacts that have parameter-influence data.
  • No fallback local analysis for missing library metadata. If repo data is absent for an artifact, skip that artifact. (Local source analysis in Step 3 is additive, not a fallback.)
  • Write deterministic output ordering:
    • Inputs by artifact
    • Classes by className
    • Methods by signature
    • Parameters by ascending index
    • Influences by dependencyKind, dynamicAccessType, trackedApi

Schema validation (shared core)

Native Build Tools performs lightweight schema validation on consumed exposed-dynamic-access.json:

Condition Action
Invalid JSON structure Skip artifact with warning
Unknown dependencyKind value Skip entry with warning
Missing required fields Skip entry with warning

Bytecode-level validation (class exists, method exists, parameter count) is performed by agent-based analysis in the metadata repository pipeline, not in Native Build Tools.

File structure in Native Build Tools

native-build-tools/
├── common/
│   └── src/main/java/org/graalvm/buildtools/
│       └── dap/  # dynamic-access-parameters
│           ├── model/
│           │   ├── DynamicAccessModels.java           # Shared data classes
│           │   ├── CallSite.java                      # Call site correlation model
│           │   └── ArgumentBinding.java               # Argument binding model
│           ├── DynamicAccessParameterDependenciesParser.java
│           ├── DynamicAccessParameterDependenciesMerger.java
│           ├── DynamicAccessParameterDependenciesSerializer.java
│           └── analysis/
│               ├── SourceAnalysisAgent.java           # Agent interface
│               ├── SourceAnalysisConfig.java          # Configuration model
│               ├── SourceAnalysisResult.java          # Analysis output model
│               ├── CorrelationAnalyzer.java           # Finds call sites to exposed methods
│               ├── IntrinsicDetector.java             # Finds direct dynamic access in user code
│               └── TrackedApis.java                   # Tracked API definitions
├── native-gradle-plugin/
│   └── src/main/java/org/graalvm/buildtools/gradle/
│       ├── tasks/
│       │   └── GenerateDynamicAccessParameterDependenciesTask.java
│       └── dsl/
│           └── SourceAnalysisOptions.java             # Gradle DSL extension
└── native-maven-plugin/
    └── src/main/java/org/graalvm/buildtools/maven/
        └── GenerateDynamicAccessParameterDependenciesMojo.java

Input from metadata repository

The metadata repository publishes per-artifact method data at:

exposed-dynamic-access/<groupId>/<artifactId>/<metadataVersion>/exposed-dynamic-access.json

Native Build Tools reads these files for each classpath artifact, reusing the metadata-version already resolved by the existing reachability metadata logic.

Missing exposed-dynamic-access.json files are not errors; artifacts without parameter influence data are simply omitted from the output.

Agent-based source analysis (Step 3)

Step 3 performs optional neural/agent-based analysis of user source code to enrich repository data with local context. This step is optional and additive—it does not replace or fallback for missing repository data.

Purpose

Repository data answers: "Which library methods expose parameters to dynamic access?"

Local analysis answers:

  • Correlation: "Where does user code call those exposed methods, and with what arguments?"
  • Intrinsic: "What direct dynamic access calls exist in user code itself?"

Together, these provide a complete picture of dynamic access patterns across the entire application.

Analysis modes

Mode Input Detects Output
Correlation User bytecode/source + fetched exposures Call sites invoking exposed library methods callSites per exposed method
Intrinsic User bytecode/source Direct calls to tracked APIs in user code dynamicAccessExposures (same schema as library exposures)

Correlation analysis

Finds locations in user code that invoke methods from exposed-dynamic-access.json:

{
  "callSites": [
    {
      "exposedMethod": {
        "className": "com.example.Library",
        "signature": "load(java.lang.String, boolean) -> java.lang.Class"
      },
      "callerLocation": {
        "className": "com.myapp.Main",
        "methodSignature": "initialize(java.lang.String) -> void",
        "lineNumber": 42
      },
      "argumentBindings": [
        {
          "parameterIndex": 0,
          "bindingKind": "parameter_passthrough",
          "sourceParameter": {
            "index": 0,
            "name": "configName"
          }
        },
        {
          "parameterIndex": 1,
          "bindingKind": "constant",
          "constantValue": "true"
        }
      ]
    }
  ]
}

Argument binding kinds:

bindingKind Meaning Implication
parameter_passthrough Caller's parameter flows directly to exposed method Propagates exposure up the call chain
parameter_derived Value computed from caller's parameter Propagates exposure with derived kind
constant Literal/compile-time constant Statically resolvable, no exposure propagation
field_reference Instance or static field May require field tracking
method_return Return value from another method call May require interprocedural analysis
unknown Could not determine binding Conservative: assume dynamic

Intrinsic dynamic access detection

Finds direct calls to tracked APIs within user code and applies the same classification rules as library analysis:

{
  "dynamicAccessExposures": [
    {
      "className": "com.myapp.PluginLoader",
      "methods": [
        {
          "signature": "loadPlugin(java.lang.String) -> java.lang.Object",
          "parameters": [
            {
              "index": 0,
              "influences": [
                {
                  "dependencyKind": "direct",
                  "dynamicAccessType": "reflection",
                  "trackedApi": "java.lang.Class#forName(java.lang.String)",
                  "location": {
                    "lineNumber": 15
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

The schema matches library exposures but adds location.lineNumber for user code (since source is available).

Tracked APIs

The agent detects calls to these dynamic access APIs:

Category Tracked APIs
Reflection Class.forName, Class.getMethod, Class.getDeclaredMethod, Class.getField, Class.getDeclaredField, Class.getConstructor, Class.getDeclaredConstructor, Class.newInstance, Constructor.newInstance, Method.invoke, Field.get, Field.set
Resources Class.getResource, Class.getResourceAsStream, ClassLoader.getResource, ClassLoader.getResourceAsStream
Proxies Proxy.newProxyInstance, Proxy.getProxyClass
Serialization ObjectInputStream.readObject, ObjectOutputStream.writeObject
JNI System.loadLibrary, System.load, Runtime.loadLibrary, Runtime.load

Agent implementation options

The analysis can be performed by:

  1. LLM-based agent: Analyzes source code with language model, suitable for complex dataflow patterns
  2. Static analysis agent: Traditional dataflow analysis (e.g., Soot, WALA, SpotBugs plugin)
  3. Hybrid: LLM for classification decisions, static analysis for call graph extraction

The agent interface is:

Input:
  - User source/bytecode root paths
  - Fetched exposed-dynamic-access.json files (from step 2)
  - Configuration (analysis depth, timeout, tracked APIs)

Output:
  - callSites: correlation data
  - dynamicAccessExposures: intrinsic dynamic access in user code

Configuration

// Gradle
graalvmNative {
    agent {
        sourceAnalysis {
            enabled = true                    // default: false
            mode = "correlation+intrinsic"    // or "correlation" or "intrinsic"
            sourcePaths = [sourceSets.main.java.srcDirs]
            maxAnalysisDepth = 5              // call chain depth limit
            timeoutSeconds = 300
        }
    }
}
<!-- Maven -->
<configuration>
    <sourceAnalysis>
        <enabled>true</enabled>
        <mode>correlation+intrinsic</mode>
        <maxAnalysisDepth>5</maxAnalysisDepth>
        <timeoutSeconds>300</timeoutSeconds>
    </sourceAnalysis>
</configuration>

Integration with merge step

When source analysis is enabled, step 4 (merge) combines:

  1. Library exposures (from repository, step 2)
  2. User code exposures (from intrinsic detection as dynamicAccessExposures, step 3)
  3. Call site data (from correlation analysis, step 3)

The merged output distinguishes sources via origin field:

{
  "classes": [
    {
      "className": "com.example.Library",
      "origin": "repository",
      "methods": [...]
    },
    {
      "className": "com.myapp.PluginLoader",
      "origin": "source_analysis",
      "methods": [...]
    }
  ],
  "callSites": [...]
}

Plugin wiring

  • Gradle:
    • Register the task per binary, using that binary's runtime classpath
    • Do not auto-attach it to nativeCompile, nativeTest, or build-report classpath wiring
  • Maven:
    • Resolve against the project runtime artifacts
    • Keep it as an explicit goal, not an automatic lifecycle side effect beyond its own invocation
  • Reuse the existing metadata repository configuration knobs in both plugins; do not introduce a second repository config path.

Test Plan

Shared-core unit tests

  • Parses a repo exposed-dynamic-access.json file with direct, derived, and control_flow influences
  • Merges methods from multiple artifacts, grouping by class with deterministic ordering
  • Omits classes/methods with no influenced parameters
  • Emits repositoryAvailable: false with empty classes array when repo root is unavailable

Schema validation tests

Test case Expected
Valid JSON with all required fields Include in output
Invalid JSON structure Skip artifact with warning
Unknown dependencyKind value Skip entry with warning
Missing required fields Skip entry with warning

Version-resolution tests

  • Verify that version resolution delegates to existing reachability metadata logic
  • Artifact present on classpath but missing sidecar JSON is skipped

Source analysis unit tests (Step 3)

Correlation analysis tests

Test case Expected
Call to exposed method with parameter passthrough bindingKind: parameter_passthrough with source parameter info
Call to exposed method with constant argument bindingKind: constant with constant value
Call to exposed method with derived value bindingKind: parameter_derived
Call to exposed method with field reference bindingKind: field_reference
Call to exposed method with method return value bindingKind: method_return
Unresolvable argument expression bindingKind: unknown
No calls to exposed methods in user code Empty callSites array

Intrinsic detection tests

Test case Expected
Direct Class.forName(param) direct influence on parameter
Class.forName(prefix + param) derived influence on parameter
if (flag) Class.forName("Literal") control_flow influence on flag parameter
Nested call chain to tracked API Correct composed dependency kind
No dynamic access in user code Empty userCodeExposures

Integration tests

  • Correlation + intrinsic combined produces merged output with both origins
  • Source analysis disabled produces output with empty callSites array and no source_analysis origin entries
  • Analysis timeout produces partial results with warning
  • Missing source paths produces empty source analysis with warning

Gradle functional tests

  • Main binary task aggregates methods from current runtime-classpath artifacts with repo data
  • Test binary task uses test runtime classpath, not main-only artifacts
  • Task is standalone and not injected into native-image classpath/build-report flow
  • Source analysis enabled: task analyzes configured source paths
  • Source analysis disabled: task skips step 3, outputs repo data only

Maven functional tests

  • Goal emits expected JSON aggregated from runtime artifacts
  • Honors configured metadata repository version/URI
  • Produces empty-but-valid output when repository data is unavailable
  • Source analysis configuration honored when enabled

Acceptance scenarios

  • One parameter with multiple influence records
  • Multiple parameters in one method
  • Same tracked API reached through different dependency kinds
  • Stable output across repeated runs
  • User code calls library exposed method with parameter passthrough → transitive exposure detected
  • User code has both library calls and direct dynamic access → both appear in output

Assumptions

  • The metadata repository will publish the new per-artifact method JSON under exposed-dynamic-access/<groupId>/<artifactId>/<metadataVersion>/exposed-dynamic-access.json.
  • That repo JSON is produced by agent-based static analysis of library source code in the metadata repository pipeline. The agent analyzes source code to identify methods whose parameters influence dynamic access calls and classifies the dependency kind (direct, derived, control_flow).
  • Bytecode-level validation (class exists, method signature matches, parameter count correct) is performed by the agent during metadata generation, not by Native Build Tools at consumption time.
  • For library artifacts: Native Build Tools consumes pre-analyzed repository data without re-analysis.
  • For user code (step 3): Native Build Tools performs local source analysis when enabled, using the same classification rules as library analysis.
  • Source analysis (step 3) is optional and additive—it enriches but does not replace repository data.
  • The source analysis agent interface is abstract; initial implementation may use static analysis (bytecode/AST), with LLM-based analysis as a future enhancement.
  • Analysis depth and timeout are configurable to balance precision vs. build time.
  • Version resolution for metadata lookup reuses the existing reachability metadata resolution logic—no new resolution mechanism is needed.
  • The first consumer is standalone JSON only. No build report UI integration is included in this change.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions