Skip to content

#4118 Fix collections no longer copied for JSpecify @NonNull sources - #4119

Open
adityaanikam wants to merge 1 commit into
mapstruct:mainfrom
adityaanikam:fix-jspecify-collection-copy-4118
Open

adityaanikam wants to merge 1 commit into
mapstruct:mainfrom
adityaanikam:fix-jspecify-collection-copy-4118

Conversation

@adityaanikam

Copy link
Copy Markdown

Fixes #4118.

CollectionAssignmentBuilder.setterWrapperNeedsSourceNullCheck() returned false whenever the source was JSpecify @NonNull, skipping the whole SetterWrapperForCollectionsAndMapsWithNullCheck wrapper. That wrapper is what generates the copy-constructor call (new ArrayList<>(...)) for a direct getter-to-setter assignment, so skipping it did not just drop the redundant null check, it dropped the defensive copy along with it: copy() started returning a target sharing the source's collection instance instead of a mutable copy, contradicting the documented collection-copy behavior.

This decouples the two concerns: direct assignments still always route through SetterWrapperForCollectionsAndMapsWithNullCheck (so the copy constructor is always generated), and a new skipNullCheck flag on that wrapper suppresses only the redundant if ( x != null ) runtime check for statically non-null sources. The local variable and copy-constructor wrapping happen either way.

Testing

  • Updated the JSpecifyCollectionPropertyMapperImpl fixture (used by the existing JSpecifyCollectionPropertyTest) to the corrected generated code.
  • Verified with a negative control: reverting only the source fix reproduces the exact original bug (direct reference assignment, no copy) in that same fixture comparison.
  • Ran the full nullcheck and collection test packages (334 tests) with no regressions.

…sources

CollectionAssignmentBuilder.setterWrapperNeedsSourceNullCheck() returned
false whenever the source was JSpecify @nonnull, skipping the whole
SetterWrapperForCollectionsAndMapsWithNullCheck wrapper. That wrapper is
what generates the copy-constructor call (new ArrayList<>(...)) for a
direct getter-to-setter assignment; skipping it did not just drop the
redundant null check, it dropped the defensive copy along with it, so
copy() started returning a target sharing the source's collection
instance instead of a mutable copy.

Decouple the two concerns: direct assignments still always route through
SetterWrapperForCollectionsAndMapsWithNullCheck (so the copy constructor
is always generated), and a new skipNullCheck flag on that wrapper
suppresses only the redundant "if ( x != null )" runtime check for
statically non-null sources. The local variable and copy-constructor
wrapping happen either way.

Updated the JSpecifyCollectionPropertyMapperImpl fixture (used by the
existing JSpecifyCollectionPropertyTest) to the corrected generated code,
and confirmed with a negative control that reverting only the source fix
reproduces the exact original bug in that same fixture comparison. Ran
the full nullcheck and collection test packages (334 tests) with no
regressions.
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.

Collections no longer copied if marked non-null

1 participant