mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-27 13:10:37 +08:00
BinaryDecoder: zero-length arrays share Array.Empty
ReadArrayElements and ReadFixedWidthArray allocated a new T[0] for a zero-length array, which every response header has (StringTable, DiagnosticInfos). A length of -1 still decodes as a null array. ServerLoadHarness read --nodes 1 (20 sessions, 4 alternating rounds): client allocB/op 1871 -> 1822 on top of the channel timer change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
77e2ca0b5a
commit
730cec1985
@@ -2432,6 +2432,12 @@ namespace Opc.Ua
|
||||
private T[] ReadArrayElements<T, TReader>(int length, TReader reader)
|
||||
where TReader : struct, IElementReader<T>
|
||||
{
|
||||
// an empty array is immutable: all share one instance.
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
int capacity = GetInitialArrayCapacity<T>(length);
|
||||
if (capacity < length)
|
||||
{
|
||||
@@ -2526,6 +2532,11 @@ namespace Opc.Ua
|
||||
/// against the remaining bytes by the caller.</param>
|
||||
private T[] ReadFixedWidthArray<T>(int length) where T : unmanaged
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// The caller verified the length against the remaining bytes. If
|
||||
// they are unknown the array grows with the bytes actually read.
|
||||
int capacity = GetRemainingLength() >= 0
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Tests;
|
||||
@@ -767,6 +768,33 @@ namespace Opc.Ua.Types.Tests.Encoders
|
||||
Assert.That(result.Count, Is.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zero-length arrays, such as the empty StringTable of every response
|
||||
/// header, decode as empty, not null, and allocate nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ReadZeroLengthArraysReturnsEmptyWithoutAllocating()
|
||||
{
|
||||
ITelemetryContext telemetryContext = NUnitTelemetryContext.Create();
|
||||
var messageContext = ServiceMessageContext.CreateEmpty(telemetryContext);
|
||||
byte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF];
|
||||
using var decoder = new BinaryDecoder(buffer, messageContext);
|
||||
|
||||
ArrayOf<string?> strings = decoder.ReadStringArray(null);
|
||||
ArrayOf<int> integers = decoder.ReadInt32Array(null);
|
||||
ArrayOf<string?> nullStrings = decoder.ReadStringArray(null);
|
||||
|
||||
Assert.That(strings.IsNull, Is.False);
|
||||
Assert.That(strings.Count, Is.Zero);
|
||||
Assert.That(MemoryMarshal.TryGetArray(strings.Memory, out ArraySegment<string?> stringArray), Is.True);
|
||||
Assert.That(stringArray.Array, Is.SameAs(Array.Empty<string?>()));
|
||||
Assert.That(integers.IsNull, Is.False);
|
||||
Assert.That(integers.Count, Is.Zero);
|
||||
Assert.That(MemoryMarshal.TryGetArray(integers.Memory, out ArraySegment<int> integerArray), Is.True);
|
||||
Assert.That(integerArray.Array, Is.SameAs(Array.Empty<int>()));
|
||||
Assert.That(nullStrings.IsNull, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadEncodeableArrayReturnsMultipleElements()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user