diff --git a/Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs b/Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs
new file mode 100644
index 0000000000..5429c90f7b
--- /dev/null
+++ b/Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections;
+using System.Linq;
+using NUnit.Framework;
+using UnityEngine.InputSystem;
+using UnityEngine.TestTools;
+
+///
+/// Test suite to verify test fixture API published in .
+///
+///
+/// This test fixture captures confusion around usage reported in
+/// https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637.
+///
+internal class InputTestFixtureTests : InputTestFixture
+{
+ private Keyboard correctlyUsedDevice;
+ private Keyboard incorrectlyUsedDevice;
+
+ [OneTimeSetUp]
+ public void UnitySetup()
+ {
+ // This is incorrect use since it will add a device to the actual input system since it executes before
+ // the InputTestFixture.Setup() method. Hence, after Setup() has executed the device is not part of
+ // the test input system instance.
+ incorrectlyUsedDevice = InputSystem.AddDevice();
+ Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);
+ }
+
+ [OneTimeTearDown]
+ public void UnityTearDown()
+ {
+ // Once InputTestFixture.TearDown() has executed, the state stack will have been popped and the keyboard
+ // we added before entering the test fixture should have been restored.
+ Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);
+ }
+
+ [SetUp]
+ public override void Setup()
+ {
+ // At this point we are still using the actual system so our device created from UnitySetup() should still
+ // exist with the context.
+ Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.True);
+
+ // This is expected usage pattern, first calling base.Setup() when overriding Setup like this, then
+ // creating a fake device via the test fixture instance that only lives with the test context.
+ base.Setup();
+ correctlyUsedDevice = InputSystem.AddDevice();
+
+ // Since we have now entered a temporary test state our device created in UnitySetup() will no longer exist
+ // with this context.
+ Assert.That(InputSystem.devices.Contains(incorrectlyUsedDevice), Is.False);
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ // This is expected usage pattern, we might want to do something with the device here, but it needs
+ // to happen before base.TearDown() since it would delete the fake device.
+ Assert.That(InputSystem.devices.Contains(correctlyUsedDevice), Is.True);
+ InputSystem.RemoveDevice(correctlyUsedDevice);
+
+ // Restore state
+ base.TearDown();
+
+ // Our test device should no longer exist with the system since we are back to real instance
+ Assert.That(InputSystem.devices.Contains(correctlyUsedDevice), Is.False);
+ }
+
+ #region Editor playmode tests
+
+ [Test]
+ public void Press_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
+ {
+ Press(correctlyUsedDevice.spaceKey);
+ Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.True);
+ }
+
+ [Test]
+ public void Press_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
+ {
+ Assert.That(incorrectlyUsedDevice.spaceKey.isPressed, Is.False);
+ Assert.Throws(() => Press(incorrectlyUsedDevice.spaceKey));
+ }
+
+ [Test]
+ public void Release_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
+ {
+ Press(correctlyUsedDevice.spaceKey);
+ Release(correctlyUsedDevice.spaceKey);
+ Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.False);
+ }
+
+ [Test]
+ public void Release_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
+ {
+ Assert.That(incorrectlyUsedDevice.spaceKey.isPressed, Is.False);
+ Assert.Throws(() => Release(incorrectlyUsedDevice.spaceKey));
+ }
+
+ [Test]
+ public void PressAndRelease_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
+ {
+ PressAndRelease(correctlyUsedDevice.spaceKey);
+ Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.False);
+ }
+
+ [Test]
+ public void PressAndRelease_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
+ {
+ PressAndRelease(incorrectlyUsedDevice.spaceKey);
+ Assert.Throws(() => PressAndRelease(incorrectlyUsedDevice.spaceKey));
+ }
+
+ [Test]
+ public void Click_ShouldMutateDeviceState_WithinPlayModeTestFixtureContext()
+ {
+ Click(correctlyUsedDevice.spaceKey);
+ Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.False);
+ }
+
+ [Test]
+ public void Click_ShouldThrow_WithinPlayModeTestFixtureContextIfInvalidDevice()
+ {
+ Click(correctlyUsedDevice.spaceKey);
+ Assert.Throws(() => Click(incorrectlyUsedDevice.spaceKey));
+ }
+
+ // TODO Add remaining
+
+ #endregion // Playmode tests
+
+ #region // Edit-mode tests
+
+ [UnityTest]
+ public IEnumerator Press_ShouldMutateDeviceState_WithinEditModeTestFixtureContext()
+ {
+ Press(correctlyUsedDevice.spaceKey);
+ yield return null; // Need to yield control to see change in next frame when running in edit-mode
+ Assert.That(correctlyUsedDevice.spaceKey.isPressed, Is.True);
+ }
+
+ // TODO Add remaining tests
+
+ #endregion // Edit-mode tests
+}
diff --git a/Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs.meta b/Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs.meta
new file mode 100644
index 0000000000..8cb308e72d
--- /dev/null
+++ b/Assets/Tests/InputSystem.Editor/InputTestFixtureTests.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c3455fa1a90b4c5683d9f36ec3ff075a
+timeCreated: 1759764564
\ No newline at end of file
diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md
index a691fdd3c9..7d8c0a43db 100644
--- a/Packages/com.unity.inputsystem/CHANGELOG.md
+++ b/Packages/com.unity.inputsystem/CHANGELOG.md
@@ -10,7 +10,9 @@ however, it has to be formatted properly to pass verification tests.
## [Unreleased] - yyyy-mm-dd
-
+### Fixed
+- Fixed an issue in `DeltaStateEvent.From` where unsafe code would throw exception or crash if internal pointer `currentStatePtr` would be `null`. [ISXB-1637](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637).
+- Fixed an issue in `InputTestFixture.Set` where attempting to change state of a device not belonging to the test fixture context would result in null pointer exception or crash. [ISXB-1637](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1637).
## [1.15.0] - 2025-10-03
diff --git a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs
index 4634d67af7..ff29db8010 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Controls/InputControl.cs
@@ -996,6 +996,9 @@ protected internal uint stateOffsetRelativeToDeviceRoot
}
}
+ // Allows determining if the device has an associated state pointer
+ internal unsafe bool hasState => currentStatePtr != null;
+
// This data is initialized by InputDeviceBuilder.
internal InternedString m_Name;
internal string m_Path;
diff --git a/Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEvent.cs b/Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEvent.cs
index 9002279341..32dc3d9983 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEvent.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Events/DeltaStateEvent.cs
@@ -76,6 +76,8 @@ public static NativeArray From(InputControl control, out InputEventPtr eve
if (!device.added)
throw new ArgumentException($"Device for control '{control}' has not been added to system",
nameof(control));
+ if (control.currentStatePtr == null) // Protects statePtr assignment below
+ throw new ArgumentNullException($"Control '{control}' do not have an associated state");
ref var deviceStateBlock = ref device.m_StateBlock;
ref var controlStateBlock = ref control.m_StateBlock;
diff --git a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs
index 2727be91be..587c8790b3 100644
--- a/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs
+++ b/Packages/com.unity.inputsystem/Tests/TestFixture/InputTestFixture.cs
@@ -59,6 +59,14 @@ namespace UnityEngine.InputSystem
/// input and device discovery or removal notifications from platform code. This ensures
/// that while the test is running, input that may be generated on the machine running
/// the test will not infer with it.
+ ///
+ /// Be cautious when using and
+ /// in combination with this test fixture.
+ /// For example, any devices created prior to execution of would be added to the actual
+ /// Input System instead of the test fixture system and after has executed such devices
+ /// will no longer be valid. You may of course use these NUnit features, but it is advised to not attempt affecting
+ /// the Input System under test from those methods since it would affect the real system and not the system
+ /// under test.
///
public class InputTestFixture
{
@@ -545,8 +553,15 @@ public void Set(InputControl control, TValue state, double time
if (control == null)
throw new ArgumentNullException(nameof(control));
if (!control.device.added)
+ {
throw new ArgumentException(
$"Device of control '{control}' has not been added to the system", nameof(control));
+ }
+ if (!control.hasState)
+ {
+ throw new ArgumentException($"Control '{control}' does not have any associated state. " +
+ "Make sure the control or device was added after executing Setup().", nameof(control));
+ }
if (IsUnityTest())
queueEventOnly = true;