Skip to content

XmlSerializer - Fire UnknownAttribute for stray attributes on built-in typed elements - #131302

Merged
StephenMolloy merged 3 commits into
dotnet:mainfrom
StephenMolloy:stephenmolloy-stunning-guacamole
Jul 30, 2026
Merged

XmlSerializer - Fire UnknownAttribute for stray attributes on built-in typed elements#131302
StephenMolloy merged 3 commits into
dotnet:mainfrom
StephenMolloy:stephenmolloy-stunning-guacamole

Conversation

@StephenMolloy

Copy link
Copy Markdown
Member

Summary

XmlSerializer's UnknownAttribute event only fired for unknown attributes on elements mapped to user-defined struct types. Elements mapped to built-in types (primitives, arrays, and collections) read their content directly and never inspected attributes, so stray attributes on those elements were silently dropped and never surfaced through the event.

This fixes #96683 by making the primitive/array/collection paths walk the element's attributes the same way the struct path already does.

Approach

When an element is mapped to a primitive, array, or collection, the reader now walks the current element's attributes and raises UnknownNode/UnknownAttribute for any non-namespace attribute, mirroring the existing struct handling in WriteAttributes. The same change is applied consistently to all three readers:

  • Reflection reader (ReflectionXmlSerializationReader)
  • IL-gen reader (XmlSerializationReaderILGen, the default runtime reader)
  • Code-gen reader (XmlSerializationReader/sgen)

xsi:nil elements are deliberately skipped (guarded by GetNullAttr()). The nil marker is consumed by the subsequent ReadNull() call, and surfacing it would be wrong; this matches the struct path, where a nil element returns before its attributes are inspected.

Compatibility

There is no app-compat switch and no new public API surface. When no handler is subscribed, UnknownNode is a no-op, so behavior is unchanged unless a caller has explicitly subscribed to the UnknownAttribute event, in which case they now also get notified about attributes on built-in typed elements. Anyone subscribed to that event almost certainly wants this to work correctly.

Tests

Added two regression tests to XmlSerializerTests:

  • XmlUnknownAttributeOnBuiltInTypedMembersTest verifies that stray attributes on string/int/nullable-int/list/array members and their items are all reported, and that they do not disrupt deserialization of the element content.
  • XmlNilAttributeOnBuiltInTypedMembersNotReportedTest verifies that xsi:nil is not surfaced as an unknown attribute and still drives the expected null results.

Verified across the IL-gen and reflection-only test legs; the full XmlSerializerTests class passes (324 tests, 0 failures). The tests fail without the fix and pass with it.

Fixes #96683

Note

This pull request was authored with the assistance of GitHub Copilot.

XmlSerializer's UnknownAttribute event only fired for unknown attributes on
elements mapped to user-defined struct types. Elements mapped to built-in
types (primitives, arrays, and collections) read their content directly and
never inspected attributes, so stray attributes on them were silently dropped.

Walk the attributes on such elements and raise UnknownNode/UnknownAttribute
for any non-namespace attribute, mirroring the existing struct handling in
WriteAttributes. xsi:nil elements are skipped (via GetNullAttr) since the nil
marker is consumed by ReadNull, matching the struct path. The change is applied
to all three readers: reflection, IL-gen, and code-gen. When no handler is
subscribed UnknownNode is a no-op, so there is no behavioral change unless the
UnknownAttribute event is used.

Fixes dotnet#96683.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be1fcace-9193-484e-8e3b-ed6a4ca70d8d
Copilot AI review requested due to automatic review settings July 24, 2026 04:58
@StephenMolloy StephenMolloy self-assigned this Jul 24, 2026
@StephenMolloy StephenMolloy added this to the 11.0.0 milestone Jul 24, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a behavioral gap in XmlSerializer deserialization events: unknown (non-namespace) attributes on elements mapped to built-in types (primitives, arrays, collections) are now surfaced via UnknownNode / UnknownAttribute, aligning these paths with existing struct-mapped behavior.

Changes:

  • Add attribute-walking logic for primitive / array / collection element readers so stray attributes raise UnknownNode / UnknownAttribute (skipping xsi:nil elements).
  • Apply the same behavior across the three reader implementations (reflection-based, IL-gen, and source-gen).
  • Add regression tests validating unknown attributes are reported on built-in typed members/items, and that xsi:nil is not reported.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs Adds regression coverage for unknown attributes on built-in typed members/items and verifies xsi:nil is not surfaced as unknown.
src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs Emits IL to walk attributes for primitive/array/collection element paths and raise unknown-attribute events consistently.
src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReader.cs Updates source-gen writer to generate attribute-walking logic for primitive/array element paths.
src/libraries/System.Private.Xml/src/System/Xml/Serialization/ReflectionXmlSerializationReader.cs Adds reflection-path attribute walking for primitive/array/collection element reads.

TypeWithBuiltInTypedMembers and TypeWithNullableBuiltInTypedMembers were
defined in XmlSerializerTests.cs. That file is also compiled into the sgen
(XMLSERIALIZERGENERATORTESTS) test leg, whose SerializableAssembly baselines
are sensitive to newly added types. These types are only exercised by the
reflection and IL-gen readers and are not interesting for sgen.

Define them in SerializationTypes.RuntimeOnly.cs (a runtime-only, non-sgen
shared file) instead, and guard the two new regression tests with
'#if !XMLSERIALIZERGENERATORTESTS' so the sgen leg neither compiles nor needs
them. The tests still run in the default (IL-gen) and reflection-only legs, and
the types resolve via the existing 'using SerializationTypes;' import. No
product behavior change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be1fcace-9193-484e-8e3b-ed6a4ca70d8d
Copilot AI review requested due to automatic review settings July 24, 2026 16:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@StephenMolloy
StephenMolloy requested a review from Copilot July 24, 2026 16:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

…efinitions

- Moved TypeWithBuiltInTypedMembers and TypeWithNullableBuiltInTypedMembers
  from SerializationTypes.RuntimeOnly.cs to SerializationTypes.cs for better organization.
- This change enhances code clarity and maintains consistency in the test structure.
Copilot AI review requested due to automatic review settings July 24, 2026 18:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

@StephenMolloy
StephenMolloy marked this pull request as ready for review July 24, 2026 18:49
@StephenMolloy
StephenMolloy requested a review from mangod9 July 24, 2026 18:49
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@StephenMolloy
StephenMolloy merged commit efe647b into dotnet:main Jul 30, 2026
84 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 11.0.0, 11.0-rc1 Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XMLSerializer UnknownAttribute event does not fire on built-in types

3 participants