Structure FKeyHandle stores an internal index, which is initialized from a static atomic counter in the default constructor:
+-----
| FKeyHandle::FKeyHandle() |
| {
| static std::atomic<uint32> LastKeyHandleIndex = 1;
| Index = ++LastKeyHandleIndex;
| (...)
| check(Index != 0); // check in the unlikely event that this overflows
| }
+----- |
Although an overflow seems unlikely, several Get() and Find() functions can return a default-constructed FKeyHandle() to represent an invalid handle, which increases the counter. If such functions are used frequently (e.g. multiple times every frame), overflowing can become a real possibility on reasonably long running games.
Interestingly, a special static function FKeyHandle::Invalid() exists, which returns a handle with invalid internal index 0 without incrementing the counter. A comment in file "KeyHandle.h" mentions that this is to "avoid allocating new handles unnecessarily". However, that function is not used in several places where it might be appropriate, such as:
[Engine\Source\Runtime\Engine\Private\Curves\IntegralCurve.cpp]
FIntegralCurve::FindKey()
FIntegralCurve::FindKeyBeforeOrAt()
[Engine\Source\Runtime\Engine\Classes\Animation\AttributeCurve.cpp]
//FAttributeCurve::FindKey() // This does use FKeyHandle::Invalid()!
FAttributeCurve::FindKeyBeforeOrAt()
[Engine\Source\Runtime\Engine\Private\Curves\NameCurve.cpp]
FNameCurve::FindKey()
[Engine\Source\Runtime\Engine\Private\Curves\StringCurve.cpp]
FStringCurve::FindKey()
[Engine\Source\Runtime\Engine\Public\Curves\KeyFrameManipulator.h]
TKeyFrameManipulator::GetKeyHandleFromIndex()
[Engine\Source\Runtime\MovieScene\Private\Channels\MovieSceneChannelData.cpp]
FMovieSceneChannelData::GetHandle()
[Engine\Source\Runtime\MovieScene\Public\Channels\MovieSceneChannel.h]
FMovieSceneChannel::GetHandle()
[Engine\Source\Editor\UnrealEd\Private\SCurveEditor.cpp]
SCurveEditor::HitTestKeys()
[Engine\Source\Editor\UnrealEd\Public\SCurveEditor.h]
FSelectedTangent::FSelectedTangent()
Note: This was reported by a licensee on an actual in-development game (see linked EPS case).
Implement and execute a native function containing the following code:
+-----
| FIntegralCurve MyCurve; |
| FKeyHandle HandleTo2000 = MyCurve.AddKey(2000, 2000); |
| bool AreKeysDifferent; |
| do |
| { | FKeyHandle Handle = MyCurve.FindKeyBeforeOrAt(1337); | AreKeysDifferent = Handle != HandleTo2000; | } |
| while (AreKeysDifferent); |
| UE_LOG(LogTemp, Error, TEXT("This should never happen, but eventually does")); +----- |
After several seconds (probably less than a minute), the index of the handle returned by FIntegralCurve::FindKeyBeforeOrAt() will wrap around and hit the check() in the constructor. On a non development build or when continuing past the check(), the function above will end up finding another FKeyHandle incorrectly considered equal to "HandleTo2000".
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-333138 in the post.
| 0 |
| Component | UE - CoreTech |
|---|---|
| Affects Versions | 5.6, 5.7 |
| Created | Sep 24, 2025 |
|---|---|
| Updated | Feb 26, 2026 |