Crash when loading Actor instance with a SceneComponent that saved before the SceneComponent was overridden to be a StaticMeshComponent in its parent Blueprint

The repro steps provided should be enough, but I can provide a repro project if necessary.

We were able to fix the crash by changing the last line of FLinkerLoad::DoesSavedClassMatchActualClass from return Export.Object->GetClass()->IsChildOf(LoadClass); to return Export.Object->GetClass() == LoadClass;. However, this could potentially result in undesired behavior if there are valid cases where the Export Object Class derives from LoadClass instead of matching it exactly. At least in the case of StaticMeshComponent, which of course is a child of SceneComponent, I don’t see how it could not crash every time, since StaticMeshComponent explicitly serializes extra data (ex. Ar << LODData;) in its Serialize function, which doesn’t exist in the serialized data of SceneComponent.

So my main question is: Does it seem like our fix is good or is there a particular reason that DoesSavedClassMatchActualClass specifically checks the class using IsChildOf?

Steps to Reproduce

  1. Create a new Blueprint-only Blank project.
  2. Choose “File” then “New Level…”, then select “Empty Open Level” and click “Create”.
  3. Choose “File” then “Save Current Level…” and save the map.
  4. In the Content Browser, click “+ Add” then “Blueprint Class”. Under “ALL CLASSES”, choose “CameraActor”.
  5. Place a new instance of the created Blueprint in the map, click “Save All” in the Content Browser, then save the Blueprint and the Actor instance in the map.
  6. Click the “pin” icon in the Outliner to the left of the entry for the Actor instance that was created so that the Actor is unloaded. This is very important for reproducing the crash.
  7. Once the Actor instance is no longer loaded, open the Blueprint, select “Scene Component”, then change its “Component Class” from “SceneComponent” to “StaticMeshComponent”.
  8. Compile and save the Blueprint.
  9. Click the “pin” icon to the left of the Actor instance to try to force load the Actor.

The Editor will then freeze and crash.

Hello! Apologies for the long wait for a reply. I’m glad to see you already found a workaround that unblocks you. I’ve looked into CL 37399091 that added FLinkerLoad::DoesSavedClassMatchActualClass and will sync with the dev who implemented that to decide on the engine fix.

However, some context for the Export.Object->GetClass()->IsChildOf(LoadClass) test in FLinkerLoad::DoesSavedClassMatchActualClass() I can share already:

The lenience for related class was introduced for a specific need we had internally with reparenting blueprints where the old parent bp had a specialized ComponentClass for the natively defined root component, and the new parent bp still has the default ComponentClass. Before, that class mismatch caused all values on existing map placed instances to be reset which is especially noticeable for root components since that includes the transform - reparented BP with mismatching base component class would set all instances at world origin.

With that in mind, making FLinkerLoad::DoesSavedClassMatchActualClass stricter as an engine modification won’t immediately introduce a drawback. It should return true only iff it’s safe to call native serialize, so I agree with your modification even. I’ll return with more info once I’ve synced with devs.

Hello! I have an update for you on this bug.

First of all, I’ve added it to the public issue tracker: UE-377471.

Secondly, we prioritized fixing this one since it would be nice for ComponentClass overrides to work. CL 53640154 on //UE5/Main addresses the crash. It will also land in UE 5.8.0.

It’s similar to your fix, but still retains some leniency: when changing from BP_MyCompA to BP_MyCompB, we will still allow native serialize if their direct native parent is the same, i.e. if they are both UMyComponents then UMyComponent::Serialize can be called which will generally retain more than just properties. (In your case, it would just retain properties and may miss custom serialized data).

Thanks for reporting this and sharing your investigation!