Skip to content

Generate MatrixOf<T> properties for OneOrMoreDimension types (#3625) - #3868

Merged
marcschier merged 7 commits into
masterfrom
sgenmtrx
Jun 12, 2026
Merged

Generate MatrixOf<T> properties for OneOrMoreDimension types (#3625)#3868
marcschier merged 7 commits into
masterfrom
sgenmtrx

Conversation

@marcschier

@marcschier marcschier commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Description

Closes the original PR #3619 TODO and extends the MatrixOf<T> opt-in to every place the source generator previously fell back to Variant for ValueRank="OneOrMoreDimensions".

Generator changes (Tools/Opc.Ua.SourceGeneration.Core)

Site 1 — DataType structure fields (DataTypeGenerator.cs)

Fields declared with matrix rank now render as global::Opc.Ua.MatrixOf<T> instead of Variant, mirroring the existing ArrayOf<T> treatment for Array rank. Encode/decode is routed per the field's DataType:

Field DataType Property Encode Decode
primitive (Boolean, Int32, String, …) MatrixOf<bool> etc. WriteVariant(name, Variant.From(field)); ReadVariant(name).GetBooleanMatrix(); (etc.)
Structure / abstract structure MatrixOf<ExtensionObject> WriteVariant(name, Variant.From(field)); ReadVariant(name).GetExtensionObjectMatrix();
concrete IEncodeable (e.g. Vector) MatrixOf<Vector> WriteEncodeableMatrix(name, field); ReadEncodeableMatrix<Vector>(name);
typed enum MatrixOf<MyEnum> WriteVariant(name, Variant.From(field)); ReadVariant(name).GetEnumerationMatrix<MyEnum>();
BaseDataType / Number / Integer / UInteger MatrixOf<Variant> WriteVariant(name, Variant.From(field)); ReadVariant(name).GetVariantMatrix();

Site 2 — VariableType State classes (NodeStateGenerator.cs)

Replaces the original // TODO: matrixOf; from #3619. Concrete matrix variable types now inherit the typed <MatrixOf<T>> chain. Standard model:

  • XYArrayItemState : ArrayItemState<MatrixOf<XVType>>.Implementation<StructureBuilder<XVType>>

Abstract bases (ArrayItemType, CubeItemType, ImageItemType, NDimensionArrayItemType — all DataType="BaseDataType") keep the existing <T> early-return.

Site 3 — Property / Variable instances (ModelDesignExtensions.GetNodeStateClassName)

Adds a OneOrMoreDimensions branch after the existing Array branch and refines IsTemplateParameterRequired / IsIndeterminateType so matrix-rank concrete types are no longer treated as indeterminate. Standard model:

  • EnumDictionaryEntries: PropertyState<MatrixOf<NodeId>>
  • FailureSystemIdentifier: BaseDataVariableState<MatrixOf<byte>>

Site 4 — Method parameters (ClientApiGenerator / ServerApiGenerator)

CollectParameters opts in to useMatrixTypeInsteadOfVariant via SupportsMatrixOf(). Zero standard-service impact (no OPC UA service declares matrix args).

Encoder / decoder API

Adds the parameterless overload MatrixOf<T> ReadEncodeableMatrix<T>(string? fieldName) where T : IEncodeable, new() to IDecoder and all implementations (BinaryDecoder, JsonDecoder, XmlDecoder, XmlParser, PubSubJsonDecoder), mirroring the existing ReadEncodeableArray<T>(string?) shape so the generator does not need a runtime encoding-id lookup.

Consumer migration

  • Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.csCreateXYArrayItemVariable updated from Variant.FromStructure(ArrayOf.Wrapped(...)) to new XVType[] { … }.ToMatrixOf(5) to match the new typed XYArrayItemState.Value surface.

Tests added

  • Generator snippet tests: GenerateMatrixValueDataType_EmitsMatrixOfPropertiesAndCalls (custom data-type matrix fields) and NodeStateGeneratorEmitsMatrixOfTemplateParameterForStandardModel (standard-model VariableType + instance types).
  • Encoder round-trip: new EncodeableMatrixRoundTripTests exercises WriteEncodeableMatrixReadEncodeableMatrix<T>(string?) through Binary, XML, and parameterless overloads with null-matrix and populated payloads.
  • Extension tests: parameterised GetDotNetTypeName_OneOrMoreDimensionsValueRankWithBasicDataTypes_ReturnsCorrectMatrixOfType and IsTemplateParameterRequired_OneOrMoreDimensionsWith… tables; SupportsMatrixOf coverage including DiagnosticInfo → false and ArgumentNullException for null receiver.
  • Test resource models (TestDataDesign.xml, DemoModel.xml, TestModel.xml): new MatrixValueDataType / SampleStructureWithMatrixFields exercise every branch end-to-end through the existing GenerateAndCompileDesignFileAsync integration test.

Documentation

Docs/MigrationGuide.md — new sub-section "Generated data type fields with ValueRank=OneOrMoreDimensions" with a per-BasicDataType mapping table, the IDecoder.ReadEncodeableMatrix<T>(string?) overload note for custom decoder implementations, and a sibling block covering VariableType State classes, property-state instances, and service parameters with before/after code samples.

Verification

Suite (net10.0) Result
Opc.Ua.Types.Tests 7553 passed
Opc.Ua.SourceGeneration.Core.Tests 3623 passed, 8 skipped (analyzer-conditional)
Opc.Ua.SourceGeneration.Stack.Tests 90 passed
Opc.Ua.SourceGeneration.Tests 40 passed
Opc.Ua.Server.Tests (focused on ReferenceServer/ArrayItem) 37 passed
Full UA.slnx build 0 errors (42 pre-existing unrelated CA2007 warnings)

The // TODO: matrixOf; comment is gone from NodeStateGenerator.cs.

Related Issues

Checklist

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • I have verified that my changes do not introduce (new) build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .net framework and .net 10, and all passed.
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

Closes the original PR #3619 TODO and extends the MatrixOf<T> opt-in to four sites in the source generator:

1. DataType structure fields: typed properties + Variant.From / WriteEncodeableMatrix encode/decode.

2. VariableType State classes: typed template parameter (e.g. XYArrayItemState<MatrixOf<XVType>>).

3. PropertyState / BaseDataVariableState instances: typed (e.g. EnumDictionaryEntries: PropertyState<MatrixOf<NodeId>>, FailureSystemIdentifier: BaseDataVariableState<MatrixOf<byte>>).

4. Client/Server API method parameters: typed MatrixOf<T> instead of Variant.

DiagnosticInfo + abstract numeric bases (BaseDataType/Number/Integer/UInteger) keep the Variant fallback.

Also adds the parameterless IDecoder.ReadEncodeableMatrix<T>(string) overload across BinaryDecoder/JsonDecoder/XmlDecoder/XmlParser and the PubSub wrapper.

Fixes #3625
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.02%. Comparing base (4d613d1) to head (deebb5d).

Files with missing lines Patch % Lines
...rceGeneration.Core/Generators/DataTypeGenerator.cs 80.00% 15 Missing and 7 partials ⚠️
...rceGeneration.Core/Schema/ModelDesignExtensions.cs 75.67% 9 Missing ⚠️
Stack/Opc.Ua.Types/Encoders/JsonDecoder.cs 66.66% 5 Missing and 3 partials ⚠️
...raries/Opc.Ua.PubSub/Encoding/PubSubJsonDecoder.cs 0.00% 4 Missing ⚠️
Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs 92.85% 0 Missing and 1 partial ⚠️
Stack/Opc.Ua.Types/Encoders/XmlParser.cs 93.33% 0 Missing and 1 partial ⚠️
...ceGeneration.Core/Generators/NodeStateGenerator.cs 66.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3868      +/-   ##
==========================================
+ Coverage   72.97%   73.02%   +0.04%     
==========================================
  Files         838      838              
  Lines      139808   140020     +212     
  Branches    24364    24398      +34     
==========================================
+ Hits       102030   102244     +214     
+ Misses      28858    28850       -8     
- Partials     8920     8926       +6     
Files with missing lines Coverage Δ
Stack/Opc.Ua.Types/BuiltIn/MatrixOf.cs 77.32% <100.00%> (+1.54%) ⬆️
Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs 89.87% <100.00%> (+0.06%) ⬆️
...ceGeneration.Core/Generators/ClientApiGenerator.cs 87.95% <100.00%> (+0.04%) ⬆️
...ceGeneration.Core/Generators/ServerApiGenerator.cs 91.89% <100.00%> (+0.07%) ⬆️
Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs 70.02% <92.85%> (+0.27%) ⬆️
Stack/Opc.Ua.Types/Encoders/XmlParser.cs 82.10% <93.33%> (+0.13%) ⬆️
...ceGeneration.Core/Generators/NodeStateGenerator.cs 86.68% <66.66%> (-0.10%) ⬇️
...raries/Opc.Ua.PubSub/Encoding/PubSubJsonDecoder.cs 62.81% <0.00%> (-0.13%) ⬇️
Stack/Opc.Ua.Types/Encoders/JsonDecoder.cs 78.76% <66.66%> (-0.15%) ⬇️
...rceGeneration.Core/Schema/ModelDesignExtensions.cs 86.01% <75.67%> (-0.18%) ⬇️
... and 1 more

... and 20 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread Docs/MigrationGuide.md Outdated
Comment thread Docs/MigrationGuide.md Outdated
Comment thread Docs/MigrationGuide.md Outdated
Comment thread Stack/Opc.Ua.Types/Encoders/IDecoder.cs
Comment thread Tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeStateGeneratorTests.cs Outdated
Comment thread Tests/Opc.Ua.SourceGeneration.Core.Tests/GenerateCodeTests.cs Outdated
… strings, RS TestData

- MigrationGuide.md: use 1.5.378 (master378) 'new Variant(new Matrix(...))' patterns in before-samples (#3868 review).

- MigrationGuide.md: drop the IsTemplateParameterRequired generator-internal paragraph.

- IDecoder.cs: cite OPC UA Part 6 v1.05 section 5.2.5 (Matrix encoding) in the new ReadEncodeableMatrix overload remarks.

- NodeStateGeneratorTests.cs: drop 'Site' and PR-number references in the snippet test comments.

- Quickstarts.Servers/TestData/Generated/TestDataDesign.xml: mirror the new MatrixValueDataType in the reference server design too.

- GenerateCodeTests.cs: convert escaped-quote strings to C# 11 raw-string literals to drop the backslash noise.
@marcschier
marcschier marked this pull request as ready for review June 9, 2026 15:09
…Matrix<T>(string)

Codecov flagged the parameterless ReadEncodeableMatrix<T>(string?) overloads in

Stack/Opc.Ua.Types/Encoders/JsonDecoder.cs and Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs

as uncovered. Add three round-trip tests:

- XmlDecoderReadEncodeableMatrixNoEncodingIdRoundTrips (streaming XML)

- JsonDecoderReadEncodeableMatrixNoEncodingIdRoundTrips (positive)

- JsonDecoderReadEncodeableMatrixNoEncodingIdMissingFieldReturnsDefault (negative)

All 6 EncodeableMatrixRoundTripTests pass locally on net10.0.
Comment thread Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs
Comment thread Stack/Opc.Ua.Types/Encoders/XmlParser.cs
…on overflow

The MatrixOf<T>(ReadOnlyMemory<T>, int[]) constructor used unchecked Int32

multiplication when validating the dimensions array against the element

count: dimensions.Aggregate((a, b) => a * b). With this PR the constructor

is reachable through new attacker-controlled wire paths via the new

IDecoder.ReadEncodeableMatrix<T>(string?) overloads added in this branch,

and was already reachable via the pre-existing encoding-id matrix overload

and every BinaryDecoder.ReadVariantValue matrix branch.

Concrete reachable case (verified by the security-review sub-agent):

dimensions=[65536, 65536] + empty element array -> product wraps to 0 ==

Memory.Length, so the length-mismatch guard accepts the message and exposes

a matrix whose declared shape claims 4_294_967_296 elements. Downstream

Array.CreateInstance / Span<T> indexing converts this into OOM or

OverflowException, i.e. exception-driven decoder DoS / data-integrity

bypass.

Hardening:

1. Wrap the dimension product in checked { } so any Int32 overflow throws

   OverflowException -> ArgumentException at the constructor boundary.

2. Reject negative individual dimensions explicitly: [-1, -1] multiplies to

   the positive value 1 and would otherwise pass the length-match guard

   for a single-element payload, only to crash downstream consumers.

Tests: 3 new MatrixOfTests cases (2-dim overflow, 3-dim overflow, negative

dimension); 36/36 MatrixOfTests pass, full Opc.Ua.Types.Tests 7559 pass.

Verified by the security-review sub-agent independently; cross-checked

against Security Agent MCP scans of service tree

59eec07a-6f20-42b9-b41b-d20e0a6322da (no findings attributable to this

branch in CodeVulnerabilities, SecretsUsage, ThreatModels, PolicyCoverage,

CodeIntegrity, or AttackPaths).
…vice param contract test

Three follow-up items from plans/security-assessment.md addressed in this commit:

1. PubSubJsonDecoder.ReadEncodeableMatrix<T> (both overloads) now throws

   NotSupportedException with a clear 'PubSub JSON matrix encoding is not

   yet implemented' message, replacing the prior silent default-return

   behavior. Surfaces the gap explicitly instead of dropping attacker-

   controlled payload content. (Resolves S2 / T3.)

2. Added a HeaterStatus enum + HeaterStatusMatrix field to TestDataDesign.xml

   MatrixValueDataType, and extended GenerateMatrixValueDataType_EmitsMatrix

   OfPropertiesAndCalls in GenerateCodeTests.cs to assert the property type

   MatrixOf<HeaterStatus>, encode call Variant.From(HeaterStatusMatrix),

   and decode call GetEnumerationMatrix<HeaterStatus>(). (Resolves T1.)

3. Added MatrixServiceParameterContract_GetDotNetTypeNameMatchesGeneratorIntegration

   in ModelDesignExtensionsTests.cs - 7 parameterised cases asserting the

   exact integration composition both ClientApiGenerator.CollectParameters

   and ServerApiGenerator.CollectParameters apply, including the

   DiagnosticInfo->Variant fallback. (Resolves T2.)

Local verification: 8/8 source-gen tests pass (1 snippet + 7 contract);

PubSub 9315/9412 pass with the only failure being an unrelated [LongRunning]

MQTT MetaData timing test that does not touch ReadEncodeableMatrix code.
@marcschier marcschier added the ready Ready to merge once CI Passes label Jun 11, 2026
The HeaterStatus enum added to TestDataDesign.xml in commit 4ec55f429

(to exercise the typed-enum matrix codegen path through HeaterStatusMatrix

in MatrixValueDataType) causes the source generator to emit one additional

output file, bringing the total per-model file count for TestDataDesign in

line with the 9 files DemoModel produces.

Updated ModelGeneratorTests.GenerateAndCompileTestDataDesignTest assertion

and added an explanatory inline comment.
marcschier pushed a commit that referenced this pull request Jun 12, 2026
…anager.DoSimulation

DoSimulation can race with Dispose() on shutdown: the simulation timer fires
and gets past the m_simulationEnabled guard, then Dispose() runs (which
intentionally does NOT wait for in-flight timer callbacks - see the comment
on Dispose, lines 79-87) and disposes m_semaphore while DoSimulation is
about to call m_semaphore.Wait(). The resulting ObjectDisposedException is
caught by the outer try/catch and logged at LogLevel.Error as "Unexpected
error doing simulation", producing misleading noise in every CI test log
that disposes a ReferenceNodeManager.

The race itself is acknowledged and accepted in the Dispose() comment as a
better trade-off than blocking Dispose on a Timer.Dispose(WaitHandle). The
log entry is the only problem: it labels an expected, documented teardown
race as an unexpected error.

Filter ObjectDisposedException specifically, gated on
`m_simulationTimer is null` so the suppression only kicks in once Dispose
has actually run. A genuine premature semaphore-disposal bug elsewhere
would still surface loudly via the generic catch.

Observed in build 14598 (mac PR build for #3868), History.Tests fixture
teardown for AlarmClientIntegrationTests:
  SERVER 04:54:05.298 [Quickstarts.ReferenceServer.ReferenceNodeManager]
    Unexpected error doing simulation #1.
  [ObjectDisposedException] Cannot access a disposed object.
  Object name: 'System.Threading.SemaphoreSlim'.
@marcschier
marcschier merged commit 59374fc into master Jun 12, 2026
179 of 180 checks passed
@marcschier
marcschier deleted the sgenmtrx branch June 12, 2026 10:45
marcschier added a commit to marcschier/UA-.NETStandard that referenced this pull request Jun 14, 2026
Brings in 7 upstream commits including OPCFoundation#3866 (Managed DevOps Pool CI
migration), OPCFoundation#3865 (dotnet format sweep), OPCFoundation#3854 (V2 SetTriggering),
OPCFoundation#3868 (MatrixOf<T> properties for OneOrMoreDimension), OPCFoundation#3864
(ManagedSession revalidation tests), OPCFoundation#3863 (Part 13 Aggregates
compliance), and OPCFoundation#3828 (stop emitting unimplemented Optional children
on singleton instances).

One conflict in `UA.slnx`: upstream added two new project entries
between `ConsoleReferenceSubscriber` and `Quickstarts.Servers` —

 * `Applications/McpServer/Opc.Ua.Mcp.csproj` — upstream's renamed
   path. Our branch never adopted the rename and still ships the
   project at `Applications/Opc.Ua.Mcp/Opc.Ua.Mcp.csproj`, which is
   already listed two lines below. Dropped: the McpServer path is
   stale build artifacts only (bin/obj), no .csproj.
 * `Applications/PumpDeviceIntegrationServer/PumpDeviceIntegrationServer.csproj`
   — upstream's intentional new project. Project file exists in our
   branch; kept.

UaLens build clean (0 warnings / 0 errors).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Ready to merge once CI Passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matrix properties for generated data types

2 participants