Skip to content

fix: use Unsafe.allocateInstance for non-static inner class to avoid JDK 25 NPE (#7654) - #7699

Open
wenshao wants to merge 9 commits into
mainfrom
fix/issue-7654-inner-class-npe
Open

fix: use Unsafe.allocateInstance for non-static inner class to avoid JDK 25 NPE (#7654)#7699
wenshao wants to merge 9 commits into
mainfrom
fix/issue-7654-inner-class-npe

Conversation

@wenshao

@wenshao wenshao commented Jul 26, 2026

Copy link
Copy Markdown
Member

Problem

On JDK 25, deserializing JSON into a class containing a non-static inner class throws NullPointerException:

java.lang.NullPointerException
  at java.base/java.util.Objects.requireNonNull
  at AmzListingJsonFeedResult$Summary.<init>
  at com.alibaba.fastjson2.reader.ORG_2_5_Summary.readObject

JDK 25 adds Objects.requireNonNull(this$0) in inner class constructors. The previous approach of passing null for the enclosing instance and patching this$0 afterwards via Unsafe.putObject now fails because the NPE fires during construction.

Fix

Use Unsafe.allocateInstance to bypass the constructor entirely for non-static inner classes. The existing this$0 fixup mechanism (via Unsafe.putObject in ASM path / reflection in FieldReaderObject) continues to set the enclosing instance correctly after creation.

Changes

  • ObjectReaderCreatorASM: Route inner class constructors (parameterCount != 0) to Unsafe.allocateInstance in both createInstance generation and genCreateObject
  • ConstructorSupplier: Use Unsafe.allocateInstance instead of constructor.newInstance(new Object[1])
  • ObjectReaderAdapter: Fallback path uses Unsafe.allocateInstance when parameterCount > 0

Closes #7654

@wenshao wenshao left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

⚠️ Downgraded from Request changes to Comment: self-PR; CI failing: Test JDK 25 on windows-latest, Test JDK 21 on windows-latest, Test Reflect JDK 25 on ubuntu-24.04, Test JDK 8 on ubuntu-24.04, Test JDK 17 on windows-latest, Test JDK 21 on ubuntu-24.04, Test JDK 25 on ubuntu-24.04, Test JDK 11 on macos-latest, Test JDK 17 on ubuntu-24.04, Test JDK 8 on macos-latest, Test JDK 11 on ubuntu-24.04, Test Reflect JDK 25 on windows-latest, Test Reflect JDK 17 on macos-latest, Test JDK 11 on windows-latest, Test Reflect JDK 21 on windows-latest, Test Reflect JDK 17 on windows-latest, Test Reflect JDK 21 on ubuntu-24.04, Test Reflect JDK 11 on windows-latest, Test Reflect JDK 11 on ubuntu-24.04, Test Reflect JDK 8 on ubuntu-24.04, Test Reflect JDK 21 on macos-latest, Test Reflect JDK 8 on windows-latest, Test JDK 17 on macos-latest, Test Reflect JDK 17 on ubuntu-24.04, Test Reflect JDK 11 on macos-latest, coverage, Test Reflect JDK 8 on macos-latest, Test JDK 21 on macos-latest, Test JDK 8 on windows-latest. Reviewed. Suggestions are inline.

— qwen3.8-max-preview via Qwen Code /review

Comment thread core/src/main/java/com/alibaba/fastjson2/reader/ConstructorSupplier.java Outdated
Comment thread core/src/test/java/com/alibaba/fastjson2/issues_7000/Issue7654.java Outdated
wenshao added a commit that referenced this pull request Jul 28, 2026
- ObjectReaderCreatorASM.newObject: only emit the UNSAFE.allocateInstance/checkcast
  sequence when the enclosing type is public. ldc/checkcast are access-checked against
  the generated reader, which lives in DynamicClassLoader, so a package-private
  enclosing type failed with IllegalAccessError (Issue1202) — the actual cause of the
  CI failures on this PR.
- fastjson1-compatible Issue1082: assert a successful parse, matching the new behaviour.
- ConstructorSupplier: cache the constructor parameter type instead of cloning
  getParameterTypes() on every get().
- ObjectReaderAdapter.createInstance: make the parameterCount == 1 assumption explicit
  and keep new Object[parameterCount] for constructors with 2+ parameters.
- Issue7654: add a case whose inner class constructor dereferences the enclosing
  instance, so the fix is regression-gated on JDK < 25 as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wenshao added a commit that referenced this pull request Jul 28, 2026
- ObjectReaderCreatorASM.newObject: only emit the UNSAFE.allocateInstance/checkcast
  sequence when the enclosing type is public. ldc/checkcast are access-checked against
  the generated reader, which lives in DynamicClassLoader, so a package-private
  enclosing type failed with IllegalAccessError (Issue1202) — the actual cause of the
  CI failures on this PR.
- fastjson1-compatible Issue1082: assert a successful parse, matching the new behaviour.
- ConstructorSupplier: cache the constructor parameter type instead of cloning
  getParameterTypes() on every get().
- ObjectReaderAdapter.createInstance: make the parameterCount == 1 assumption explicit
  and keep new Object[parameterCount] for constructors with 2+ parameters.
- Issue7654: add a case whose inner class constructor dereferences the enclosing
  instance, so the fix is regression-gated on JDK < 25 as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wenshao
wenshao force-pushed the fix/issue-7654-inner-class-npe branch from bce14f1 to 0d76b79 Compare July 28, 2026 13:55

@wenshao wenshao left a comment

Copy link
Copy Markdown
Member Author

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.8-max-preview via Qwen Code /review

Comment thread core/src/test/java/com/alibaba/fastjson2/issues_7000/Issue7654.java Outdated
wenshao added 5 commits August 2, 2026 15:58
…JDK 25 NPE (#7654)

JDK 25 adds Objects.requireNonNull for the enclosing instance parameter
in inner class constructors. The previous approach of passing null and
patching this$0 afterwards now throws NPE before the patch can occur.

Use Unsafe.allocateInstance to bypass the constructor entirely for
non-static inner classes; the existing this$0 fixup via Unsafe.putObject
still sets the enclosing instance correctly after creation.
…to preserve constructor semantics

Unsafe.allocateInstance bypassed field initializers (Issue367) and
constructor logic (Issue1082). Instead, create a dummy enclosing
instance via Unsafe.allocateInstance(declaringClass) and pass it to
the inner class constructor, preserving field defaults and validation.
The real parent is still patched via Unsafe.putObject afterwards.
- ObjectReaderCreatorASM.newObject: only emit the UNSAFE.allocateInstance/checkcast
  sequence when the enclosing type is public. ldc/checkcast are access-checked against
  the generated reader, which lives in DynamicClassLoader, so a package-private
  enclosing type failed with IllegalAccessError (Issue1202) — the actual cause of the
  CI failures on this PR.
- fastjson1-compatible Issue1082: assert a successful parse, matching the new behaviour.
- ConstructorSupplier: cache the constructor parameter type instead of cloning
  getParameterTypes() on every get().
- ObjectReaderAdapter.createInstance: make the parameterCount == 1 assumption explicit
  and keep new Object[parameterCount] for constructors with 2+ parameters.
- Issue7654: add a case whose inner class constructor dereferences the enclosing
  instance, so the fix is regression-gated on JDK < 25 as well.
With -Dfastjson2.creator=reflect a non-static inner class goes through
beanInfo.creatorConstructor -> ObjectReaderNoneDefaultConstructor -> ConstructorFunction,
which the previous commits did not cover: the synthetic enclosing-instance parameter never
appears in the JSON, so it fell back to TypeUtils.getDefaultValue() and the constructor ran
with a null this$0. ConstructorFunction now recognises that parameter and allocates a bare
enclosing instance for it, matching ConstructorSupplier, ObjectReaderAdapter and the ASM path.

Found by testInnerClassConstructorDereferencingOuter, which the ASM path passes and the
reflect path did not — the Test Reflect CI jobs were failing on it.
…#7654)

- ConstructorFunction.defaultArg catches only InstantiationException, so an
  OutOfMemoryError from allocateInstance is no longer swallowed into a null
  enclosing instance, which would resurface as the NPE this PR fixes
- Issue7654 now iterates TestUtils.readerCreators(), so the ConstructorFunction
  reflection path is covered as well as the ASM path
wenshao added a commit that referenced this pull request Aug 2, 2026
- ObjectReaderCreatorASM.newObject: only emit the UNSAFE.allocateInstance/checkcast
  sequence when the enclosing type is public. ldc/checkcast are access-checked against
  the generated reader, which lives in DynamicClassLoader, so a package-private
  enclosing type failed with IllegalAccessError (Issue1202) — the actual cause of the
  CI failures on this PR.
- fastjson1-compatible Issue1082: assert a successful parse, matching the new behaviour.
- ConstructorSupplier: cache the constructor parameter type instead of cloning
  getParameterTypes() on every get().
- ObjectReaderAdapter.createInstance: make the parameterCount == 1 assumption explicit
  and keep new Object[parameterCount] for constructors with 2+ parameters.
- Issue7654: add a case whose inner class constructor dereferences the enclosing
  instance, so the fix is regression-gated on JDK < 25 as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wenshao
wenshao force-pushed the fix/issue-7654-inner-class-npe branch 2 times, most recently from 22ff6a9 to c7eeba7 Compare August 2, 2026 08:05

@wenshao wenshao left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

⚠️ Downgraded from Request changes to Comment: self-PR. ⚠️ This run could not certify that any of this diff was reviewed. Suggestions are inline. Not reviewed: the entire diff — no agent reported covering it; nobody read it. Not reviewed: the linked-issue fidelity pass, the line-by-line correctness pass, the security pass, the reuse and duplication pass, the altitude and abstraction pass, the consistency and clarity pass, the performance pass, the test-coverage pass, the open-ended audit (attacker mindset), the open-ended audit (oncall mindset), the open-ended audit (maintainer mindset), the removed-behavior audit, the cross-file consistency pass, the build-and-test check — its prompt was built, but no agent on record was launched with it. Not reviewed: verification and reverse audit — both prompts were built, but no agent was launched with either — the posted findings cannot be counted as verified, and the pass that hunts what the rest of the review missed cannot be certified.

— qwen3.8-max-preview via Qwen Code /review (v0.21.3)

Comment thread core/src/main/java/com/alibaba/fastjson2/reader/ConstructorSupplier.java Outdated
Comment thread core/src/main/java/com/alibaba/fastjson2/reader/ConstructorSupplier.java Outdated
Comment thread core/src/test/java/com/alibaba/fastjson2/issues_7000/Issue7654.java
Comment thread core/src/main/java/com/alibaba/fastjson2/reader/ConstructorFunction.java Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Member Author

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.8-max-preview via Qwen Code /review (v0.21.3)

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.

[BUG] JDK25字符串转对象包含非静态内部类时空指针, JDK21正常

1 participant