Hello! I was able to reproduce the nulled subobject via redirectors. For sure, it would be best to catch these as soon as possible.
When a blueprint loads and a subobject class isn’t found, the following message is printed to log from LinkerLoad.cpp:
FString OuterName = Export.OuterIndex.IsNull() ? LinkerRoot->GetFullName() : GetFullImpExpName(Export.OuterIndex);
FString ClassName = GetClassName(Export.ThisIndex).ToString();
UE_CLOG(Export.ObjectFlags & EObjectFlags::RF_Public, LogLinker, Warning, TEXT("Unable to load %s with outer %s because its class (%s) does not exist"), *Export.ObjectName.ToString(), *OuterName, *ClassName);
return nullptr;
You can consider modifying the engine code there to be more disruptive:
- FMessageDialog for a user popup that could inform them to blame someone
- Or a Fatal log. Personally I recommend against that because it would block non-technical team members
I also wrote an editor validator that will popup an error when saving a blueprint that has a nulled subobject that clearly stems from corruption. It uses reasoning similar to yours, about a UPROPERTY clearly shouldn’t be null if the native constructor created a default subobject for it. This validator will show the popup on save (since validators run on any asset save), but wouldn’t prevent the save. If your studio uses Epic’s SubmitTool, you can prevent people from checking in assets that fail validators though!
[Image Removed]
#include "EditorValidator_NulledSubobjects.h"
#include "Misc/DataValidation.h"
#include "UObject/FieldIterator.h"
bool UEditorValidator_NulledSubobjects::CanValidateAsset_Implementation(const FAssetData& InAssetData, UObject* InObject, FDataValidationContext& InContext) const
{
return InObject != nullptr && InObject->IsA<UBlueprint>();
}
EDataValidationResult UEditorValidator_NulledSubobjects::ValidateLoadedAsset_Implementation(const FAssetData& InAssetData, UObject* InAsset, FDataValidationContext& Context)
{
// Valid, until invalid
EDataValidationResult Result = EDataValidationResult::Valid;
if (InAsset->IsA<UBlueprint>())
{
UBlueprint* BP = Cast<UBlueprint>(InAsset);
// We will use reflection to check value of UPROPERTY pointers.
// The blueprint generated class's default object contains the blueprint's saved values.
UClass* BPGC = BP->GeneratedClass;
check(BPGC);
check(!BPGC->IsNative());
UObject* BPCDO = BPGC->GetDefaultObject();
check(BPCDO);
// The native parent class's default object's subobjects can be checked to see if the
// native constructor defines default subobjects. This is to minimize false hits.
// Find the native class by walking up the class hierarchy until a native class is found.
UClass* NativeSuperClass = BPGC;
do
{
NativeSuperClass = NativeSuperClass->GetSuperClass();
} while (!NativeSuperClass->IsNative());
check(NativeSuperClass);
UObject* NativeCDO = NativeSuperClass->GetDefaultObject();
check(NativeCDO);
// Iterate all of the native class's object pointer UPROPERTIES
for (TFieldIterator<FObjectProperty> PropertyIt(NativeSuperClass); PropertyIt; ++PropertyIt)
{
const FObjectProperty* Prop = *PropertyIt;
const FString PropClass = Prop->GetClass()->GetName();
const FString PropName = Prop->GetName();
UObject* NativeCDO_Subobject = Prop->GetObjectPtrPropertyValue_InContainer(NativeCDO);
UObject* BPCDO_Subobject = Prop->GetObjectPtrPropertyValue_InContainer(BPCDO);
// If the native CDO has a default subobject, but the blueprint CDO doesn't, it's likely a subobject being incorrectly nulled.
if (NativeCDO_Subobject && NativeCDO_Subobject->HasAnyFlags(EObjectFlags::RF_DefaultSubObject) && BPCDO_Subobject == nullptr)
{
const FString ErrorMessage = FString::Printf(TEXT("%s has an invalid subobject for property '%s'. It's null but expected non-null. Missing a class redirector?"), *InAsset->GetName(), *PropName);
Context.AddMessage(InAssetData, EMessageSeverity::Type::Error, FText::FromString(ErrorMessage));
Result = EDataValidationResult::Invalid;
}
}
}
return EDataValidationResult::Invalid;
}
I understand that ideally, you wouldn’t want the asset to save at all when a subobject clearly is null incorrectly. I’m not sure off the top of my head if EditorValidators can be used to prevent save, or if we have other ways to prevent saves under project specific conditions. I’ll ask around and respond back later.
[Attachment Removed]