UE 5.5 crash while accessing garbage data in FGameplayAttribute

Hello! While upgrading our project from 5.4 to 5.5, we found an issue with FGameplayAttribute allowing garbage data to be accessed which can cause a crash.

Steps to reproduce:

  • Create a BP reference to a gameplay attribute - in our case it was a float UPROPERTY() value
  • Delete the UPROPERTY() value but don’t remove the reference in blueprints
  • Restart editor/game, and run anything that uses the old blueprint logic

In both engine versions, an FGameplayAttribute is deserialized for the invalid property, but in 5.4 and earlier errors would be avoided because FGameplayAttribute::IsValid() returns false. In 5.5 this is no longer the case. It appears to be because the operation used for Attribute != nullptr has changed. I’ve written up a simple test here that passes in 5.4 but not 5.5:

GameplayAttributeTest.h

USTRUCT()
struct FGameplayAttributeTestPoint
{
	GENERATED_BODY()
public:
	UPROPERTY()
	int32 X = 0;
	UPROPERTY()
	int32 Y = 0;
};

GameplayAttributeTest.cpp

TEST_CASE_NAMED(FGameplayAttributeTest, "GameplayAbilities::GameplayAttribute", "[Core][UObject][SmokeFilter]")
{
	SECTION("Serialization")
	{
		FGameplayAttribute Attribute;
		Attribute.SetUProperty(FGameplayAttributeTestPoint::StaticStruct()->PropertyLink);
		
		TArray<uint8> Data;
		FMemoryWriter ByteWriter(Data);
		FObjectAndNameAsStringProxyArchive Writer(ByteWriter, false);
		FGameplayAttribute::StaticStruct()->SerializeItem(Writer, &Attribute, nullptr);
		{
			// Change the property name from X to Y.
			Data[47] = 'Y';
			Data[102] = 'Y';
		
			TFieldPath<FProperty> FieldPath;
			FMemoryReader ByteReader(Data);
			FObjectAndNameAsStringProxyArchive Reader(ByteReader, false);
		
			FGameplayAttribute::StaticStruct()->SerializeItem(Reader, &Attribute, nullptr);
			// Attribute is valid, because property Y exists.
			bool Result = Attribute.IsValid();
			CHECK(Result);
		}
		
		{
			// Change the property name from X to Z. In normal circumstances this could indicate that property Z _used_ to exist and does not anymore, but there is still a serialized reference to it on disk.
			Data[47] = 'Z';
			Data[102] = 'Z';
		
			TFieldPath<FProperty> FieldPath;
			FMemoryReader ByteReader(Data);
			FObjectAndNameAsStringProxyArchive Reader(ByteReader, false);
		
			FGameplayAttribute::StaticStruct()->SerializeItem(Reader, &Attribute, nullptr);
			// Attribute should not be valid, because property Z does not exist.
			bool Result = Attribute.IsValid();
			CHECK(!Result);
		}
	}
}