We’ve ran into this check failure a few times from various sources since upgrading to 5.6, and this is the most 100% repro I’ve found so far.
From digging, it appears that there’s some assignment of data into a TScriptArray, and the ArrayMax value doesn’t get updated. When the “Identical()” function is then called to copy from the CDO of this object, a Num() call follows which causes that check to fail. I may have some details incorrect, but I wanted to flag this before I started making changes this deep in the engine.
I’ve managed to repro this on vanilla 5.6.1 and 5.7.1.
We’re able to repro this 100% with the debug build of the editor, but in some circumstances that check manifests in Development and can bring down the editor for our teams.
template<typename UStructType>
class TOverrideResetToDefaultWithStaticUStruct
{
...
private:
/**
* Resolves the correct default value pointer for a property that may be nested within UStructType.
* Walks the property handle parent chain to figure out the correct offset from DefaultObject.
*/
static const void* ResolveDefaultValuePtr(TSharedPtr<IPropertyHandle> InPropertyHandle);
static const UStructType DefaultObject;
};
...
template<typename UStructType>
const void* TOverrideResetToDefaultWithStaticUStruct<UStructType>::ResolveDefaultValuePtr(TSharedPtr<IPropertyHandle> InPropertyHandle)
{
FProperty* Property = InPropertyHandle->GetProperty();
if (!Property)
{
return nullptr;
}
// If this property is a direct member of UStructType, use DefaultObject as the container
if (Property->GetOwnerStruct() == UStructType::StaticStruct())
{
return Property->ContainerPtrToValuePtr<void>(&DefaultObject);
}
// Property is nested deeper within UStructType - resolve the parent's default value to use as container
TSharedPtr<IPropertyHandle> ParentHandle = InPropertyHandle->GetParentHandle();
if (!ParentHandle.IsValid())
{
return nullptr;
}
const void* ParentDefaultValue = ResolveDefaultValuePtr(ParentHandle);
if (!ParentDefaultValue)
{
return nullptr;
}
return Property->ContainerPtrToValuePtr<void>(ParentDefaultValue);
}
template<typename UStructType>
bool TOverrideResetToDefaultWithStaticUStruct<UStructType>::IsResetToDefaultVisible(TSharedPtr<IPropertyHandle> InPropertyHandle)
{
FProperty* Property = InPropertyHandle->GetProperty();
const void* DefaultValuePtr = nullptr;
void* ValuePtr = nullptr;
check(Property != nullptr);
DefaultValuePtr = ResolveDefaultValuePtr(InPropertyHandle);
InPropertyHandle->GetValueData(ValuePtr);
if ((DefaultValuePtr != nullptr) && (ValuePtr != nullptr))
{
return !Property->Identical(DefaultValuePtr, ValuePtr);
}
return false;
}
template<typename UStructType>
void TOverrideResetToDefaultWithStaticUStruct<UStructType>::OnResetToDefault(TSharedPtr<IPropertyHandle> InPropertyHandle)
{
FProperty* Property = InPropertyHandle->GetProperty();
const void* DefaultValuePtr = nullptr;
void* ValuePtr = nullptr;
check(Property != nullptr);
DefaultValuePtr = ResolveDefaultValuePtr(InPropertyHandle);
InPropertyHandle->GetValueData(ValuePtr);
if ((DefaultValuePtr != nullptr) && (ValuePtr != nullptr))
{
// SetPropertyValue_DirectSingle when called on an archetype can propagate to instances.
// See class comments: this ResetToDefault override was not written for contexts involving
// inheritance, so pass in empty archetype instances list.
TArray<void*> NoArchetypeInsts;
PropertyAccessUtil::SetPropertyValue_DirectSingle(Property, DefaultValuePtr, Property, ValuePtr, NoArchetypeInsts, 0, Property->HasAnyFlags(RF_ArchetypeObject), []() { return nullptr; });
}
}