Skip to content

feat(json): add GraalVM codegen support - #3907

Open
chaokunyang wants to merge 1 commit into
apache:mainfrom
chaokunyang:graalvm_json_codegen
Open

feat(json): add GraalVM codegen support#3907
chaokunyang wants to merge 1 commit into
apache:mainfrom
chaokunyang:graalvm_json_codegen

Conversation

@chaokunyang

@chaokunyang chaokunyang commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Why?

What does this PR do?

Related issues

AI Contribution Checklist

  • Substantial AI assistance was used in this PR: yes / no
  • If yes, I included a completed AI Contribution Checklist in this PR description and the required AI Usage Disclosure.
  • If yes, my PR description includes the required ai_review summary and screenshot evidence or equivalent persisted links of the final clean AI review results from both fresh reviewers described in AI_POLICY.md, the Fory-guided reviewer and the independent general reviewer, on the current PR diff or current HEAD after the latest code changes.

Does this PR introduce any user-facing change?

  • Does this PR introduce any public API change?
  • Does this PR introduce any binary protocol compatibility change?

Benchmark

@wenshao wenshao left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline.

— qwen3-coder-plus via Qwen Code /review (v0.21.2)

Comment on lines 131 to 135
} catch (ReflectiveOperationException e) {
throw new ForyJsonException(
"Cannot create SQL JSON type " + constructor.getDeclaringClass(), e);
throw new ForyJsonException("Cannot create SQL JSON type " + type, e);
} catch (Throwable e) {
throw new ForyJsonException("Cannot create SQL JSON type " + type, e);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The catch (ReflectiveOperationException e) block is redundant — it is entirely subsumed by the subsequent catch (Throwable e) block, and both perform the identical wrapping. — Concrete cost: any future change to error handling (e.g., adding context, unwrapping InvocationTargetException) must be applied in two places or the blocks silently diverge.

Suggested change
} catch (ReflectiveOperationException e) {
throw new ForyJsonException(
"Cannot create SQL JSON type " + constructor.getDeclaringClass(), e);
throw new ForyJsonException("Cannot create SQL JSON type " + type, e);
} catch (Throwable e) {
throw new ForyJsonException("Cannot create SQL JSON type " + type, e);
}
} catch (Throwable e) {
throw new ForyJsonException("Cannot create SQL JSON type " + type, e);
}

— qwen3-coder-plus via Qwen Code /review

Comment on lines +262 to +270
private void convert(Class<?> type) throws IOException {
if (!type.isPrimitive()) {
if (type != Object.class) {
this.type(0xc0, internalName(type));
}
return;
}
Class<?> boxType = BOX_TYPES.get(type);
this.type(0xc0, internalName(boxType));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] JsonCreatorCodegen emits unboxing bytecode for 8 primitive types but only int is exercised through @JsonCreator constructors in the integration tests; long, double, boolean, byte, short, char, and float paths have no test coverage. — Concrete cost: if a future change to convert() or boxTypes() introduces a wrong box type or unboxing method for long, a @JsonCreator with a long parameter would produce a VerifyError or silent value truncation in the native image with no test to catch it.

Consider adding a @JsonCreator model to ForyJsonExample that exercises at least long and double constructor parameters.

— qwen3-coder-plus via Qwen Code /review

return methods;
}

private boolean generateConfigurations(DuringAnalysisAccess access) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Unused DuringAnalysisAccess access parameter — the method body never references access; it works entirely through instance fields (hostedConfigurations, processedModels, reachableMixins, processedGenerations). This is dead code left over from the removed registerGeneratedCodec path that used access.findClassByName. — Concrete cost: compiling with -Xlint:unused flags this as a warning, and a -Werror CI configuration would fail the build; the parameter also creates false coupling at the call site.

Suggested change
private boolean generateConfigurations(DuringAnalysisAccess access) {
private boolean generateConfigurations() {

— qwen3-coder-plus via Qwen Code /review

@Internal
public final class JsonGeneratedClassRegistry {
private static Map<JsonCodegenKey, MutableConfiguration> pending = new HashMap<>();
private static Map<JsonCodegenKey, Configuration> configurations = Collections.emptyMap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] configurations is written under synchronized in register() but read without synchronization or volatile in the public configuration() method — a JMM data race on the reference. In the current GraalVM-only lifecycle the native-image heap-snapshot provides a happens-before edge, so the race does not manifest. However, configuration() is @Internal public and called from 12 unsynchronized sites in JsonSharedRegistry. If any future code path calls configuration() concurrently with register() without an external synchronization barrier, the reading thread can see the stale Collections.emptyMap() initial value. — Concrete cost: every native*Class() lookup returns null, silently falling back to interpreted codecs.

Suggested change
private static Map<JsonCodegenKey, Configuration> configurations = Collections.emptyMap();
private static volatile Map<JsonCodegenKey, Configuration> configurations = Collections.emptyMap();

— qwen3-coder-plus via Qwen Code /review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants