Skip to content

Commit 91762ca

Browse files
authored
FIX: memory alignment issue when deserializing InputEventTraces (ISXB-317) (#1596)
* FIX: prevent infinite loop when deserializing InputEventTrace * review changes. replace alignment value with enum constant * review changes: add more robust test and fix for incorrect event size
1 parent 156bd1f commit 91762ca

3 files changed

Lines changed: 97 additions & 8 deletions

File tree

Assets/Tests/InputSystem/CoreTests_Events.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,94 @@ public void Events_WhenTraceIsFull_WillStartOverwritingOldEvents()
14141414
}
14151415
}
14161416

1417+
[Test]
1418+
[Category("Events")]
1419+
public void Events_CanDeserializeInputEventTraceFromMemory()
1420+
{
1421+
var dataStream = new MemoryStream();
1422+
long sourceTraceEventSize = 0;
1423+
long sourceEventCount = 0;
1424+
1425+
var gamepad = InputSystem.AddDevice<Gamepad>();
1426+
1427+
// Generate a trace
1428+
using (var trace = new InputEventTrace())
1429+
{
1430+
trace.Enable();
1431+
1432+
// Enough events that the aggregate of any padding introduced due to alignment would
1433+
// start to skew data in last event
1434+
Press(gamepad.buttonEast);
1435+
Set(gamepad.leftStick, new Vector2(0.123f, 0.234f));
1436+
Press(gamepad.buttonSouth);
1437+
Press(gamepad.buttonWest);
1438+
Press(gamepad.buttonNorth);
1439+
1440+
Release(gamepad.buttonEast);
1441+
Release(gamepad.buttonSouth);
1442+
Release(gamepad.buttonWest);
1443+
Release(gamepad.buttonNorth);
1444+
1445+
Press(gamepad.buttonEast);
1446+
Press(gamepad.buttonSouth);
1447+
Press(gamepad.buttonWest);
1448+
Press(gamepad.buttonNorth);
1449+
Set(gamepad.rightStick, new Vector2(-0.123f, -0.234f));
1450+
1451+
InputSystem.Update();
1452+
1453+
Assert.That(trace.eventCount, Is.EqualTo(14));
1454+
sourceEventCount = trace.eventCount;
1455+
sourceTraceEventSize = trace.totalEventSizeInBytes;
1456+
1457+
// Serialize trace to memory
1458+
trace.WriteTo(dataStream);
1459+
trace.Disable();
1460+
dataStream.Flush();
1461+
}
1462+
1463+
// Deserialize trace from memory into a fresh new buffer
1464+
using (var trace = new InputEventTrace())
1465+
{
1466+
dataStream.Position = 0;
1467+
trace.ReadFrom(dataStream);
1468+
1469+
// Check that we have read the correct number of bytes
1470+
Assert.That(trace.allocatedSizeInBytes, Is.EqualTo(sourceTraceEventSize));
1471+
Assert.That(trace.totalEventSizeInBytes, Is.EqualTo(sourceTraceEventSize));
1472+
1473+
// This line should not cause an infinite loop
1474+
trace.Replay().PlayAllEvents();
1475+
1476+
// Collect content and event count manually using enumeration
1477+
var retrievedEvents = new List<InputEventPtr>();
1478+
int eventCount = 0;
1479+
long aggregateEventSize = 0;
1480+
var current = default(InputEventPtr);
1481+
while (trace.GetNextEvent(ref current))
1482+
{
1483+
eventCount++;
1484+
aggregateEventSize += current.sizeInBytes;
1485+
retrievedEvents.Add(current);
1486+
}
1487+
1488+
// Check sum of enumerated values and header values match
1489+
Assert.That(trace.eventCount, Is.EqualTo(sourceEventCount));
1490+
Assert.That(eventCount, Is.EqualTo(trace.eventCount));
1491+
Assert.That(trace.totalEventSizeInBytes, Is.GreaterThanOrEqualTo(aggregateEventSize));
1492+
1493+
// Check event data is correct
1494+
Assert.That(retrievedEvents[0].deviceId, Is.EqualTo(gamepad.deviceId));
1495+
Assert.That(gamepad.buttonEast.ReadUnprocessedValueFromEvent(retrievedEvents[0]), Is.EqualTo(1));
1496+
Assert.That(gamepad.buttonSouth.ReadUnprocessedValueFromEvent(retrievedEvents[0]), Is.EqualTo(0));
1497+
1498+
// Especially the last one
1499+
var lastIdx = eventCount - 1;
1500+
Assert.That(retrievedEvents[lastIdx].deviceId, Is.EqualTo(gamepad.deviceId));
1501+
Assert.That(gamepad.rightStick.ReadUnprocessedValueFromEvent(retrievedEvents[lastIdx]), Is.EqualTo(new Vector2(-0.123f, -0.234f)));
1502+
}
1503+
}
1504+
14171505
[Test]
14181506
[Category("Events")]
14191507
public void Events_CanClearEventTrace()

Packages/com.unity.inputsystem/CHANGELOG.md

100755100644
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ however, it has to be formatted properly to pass verification tests.
1414
- Fixed `ArgumentNullException` when opening the Prefab Overrides window and selecting a component with an `InputAction`.
1515
- Fixed `{fileID: 0}` getting appended to `ProjectSettings.asset` file when building a project ([case ISXB-296](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-296)).
1616
- Fixed `Type of instance in array does not match expected type` assertion when using PlayerInput in combination with Control Schemes and Interactions ([case ISXB-282](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-282)).
17+
- Fixed Memory alignment issue with deserialized InputEventTraces that could cause infinite loops when playing back replays ([case ISXB-317](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-317)).
1718
- Fixed an InvalidOperationException when using Hold interaction, and by extension any interaction that changes to performed state after a timeout ([case ISXB-332](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-330)).
1819
- Fixed `Given object is neither an InputAction nor an InputActionMap` when using `InputActionTrace` on input action from an input action asset ([case ISXB-29](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-29)).
1920

Packages/com.unity.inputsystem/InputSystem/Events/InputEventTrace.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public void ReadFrom(Stream stream)
342342
}
343343
else
344344
{
345-
buffer = (byte*)UnsafeUtility.Malloc((long)totalEventSizeInBytes, 4, Allocator.Persistent);
345+
buffer = (byte*)UnsafeUtility.Malloc((long)totalEventSizeInBytes, InputEvent.kAlignment, Allocator.Persistent);
346346
m_EventBufferSize = (long)totalEventSizeInBytes;
347347
}
348348
try
@@ -373,8 +373,8 @@ public void ReadFrom(Stream stream)
373373
fixed(byte* tempBufferPtr = tempBuffer)
374374
UnsafeUtility.MemCpy(tailPtr, tempBufferPtr, remainingSize);
375375

376-
tailPtr += remainingSize;
377-
totalEventSize += eventSizeInBytes;
376+
tailPtr += remainingSize.AlignToMultipleOf(InputEvent.kAlignment);
377+
totalEventSize += eventSizeInBytes.AlignToMultipleOf(InputEvent.kAlignment);
378378

379379
if (tailPtr >= endPtr)
380380
break;
@@ -501,7 +501,7 @@ public bool Resize(long newBufferSize, long newMaxBufferSize = -1)
501501
newMaxBufferSize = newBufferSize;
502502

503503
// Allocate.
504-
var newEventBuffer = (byte*)UnsafeUtility.Malloc(newBufferSize, 4, Allocator.Persistent);
504+
var newEventBuffer = (byte*)UnsafeUtility.Malloc(newBufferSize, InputEvent.kAlignment, Allocator.Persistent);
505505
if (newEventBuffer == default)
506506
return false;
507507

@@ -521,7 +521,7 @@ public bool Resize(long newBufferSize, long newMaxBufferSize = -1)
521521
for (var i = 0; i < m_EventCount; ++i)
522522
{
523523
var eventSizeInBytes = fromPtr.sizeInBytes;
524-
var alignedEventSizeInBytes = eventSizeInBytes.AlignToMultipleOf(4);
524+
var alignedEventSizeInBytes = eventSizeInBytes.AlignToMultipleOf(InputEvent.kAlignment);
525525

526526
// We only start copying once we know that the remaining events we have fit in the new buffer.
527527
// This way we get the newest events and not the oldest ones.
@@ -753,7 +753,7 @@ private byte* m_EventBufferTail
753753

754754
private void Allocate()
755755
{
756-
m_EventBuffer = (byte*)UnsafeUtility.Malloc(m_EventBufferSize, 4, Allocator.Persistent);
756+
m_EventBuffer = (byte*)UnsafeUtility.Malloc(m_EventBufferSize, InputEvent.kAlignment, Allocator.Persistent);
757757
}
758758

759759
private void Release()
@@ -805,7 +805,7 @@ private void OnInputEvent(InputEventPtr inputEvent, InputDevice device)
805805
if (m_EventBuffer == default)
806806
return;
807807

808-
var bytesNeeded = inputEvent.sizeInBytes.AlignToMultipleOf(4);
808+
var bytesNeeded = inputEvent.sizeInBytes.AlignToMultipleOf(InputEvent.kAlignment);
809809

810810
// Make sure we can fit the event at all.
811811
if (bytesNeeded > m_MaxEventBufferSize)
@@ -830,7 +830,7 @@ private void OnInputEvent(InputEventPtr inputEvent, InputDevice device)
830830
// If we haven't reached the max size yet, grow the buffer.
831831
if (m_EventBufferSize < m_MaxEventBufferSize && !m_HasWrapped)
832832
{
833-
var increment = Math.Max(m_GrowIncrementSize, bytesNeeded.AlignToMultipleOf(4));
833+
var increment = Math.Max(m_GrowIncrementSize, bytesNeeded.AlignToMultipleOf(InputEvent.kAlignment));
834834
var newBufferSize = m_EventBufferSize + increment;
835835
if (newBufferSize > m_MaxEventBufferSize)
836836
newBufferSize = m_MaxEventBufferSize;

0 commit comments

Comments
 (0)