Why does IsValid() sometimes throw this assert?

I’ll do something like this:

if(IsValid(somePointer)) { ... }

Most of the time this returns what you’d expect: true if the pointer is valid, false if the pointer is null or points to a destroyed/invalid object. But sometimes, it will instead throw this:

Assertion failed: Index >= 0 [File:c:\repositories\ue-deploy\unrealengine\engine\source\runtime\coreuobject\public\UObject/UObjectArray.h] [Line: 455] 

The same exact code will work correctly on one run of the game and then throw the assert on the next, or vice-versa. Just… roll the dice.

In the particular case I’m looking at right now, the pointer in question is set to nullptr in the header declaration, so it’s not like it’s uninitialized garbage or anything. I have extremely straightforward, very basic logic for spawning an actor to this pointer some time after starting a game session, and then the actor is destroyed some time after that. I’ve seen this assert happen when checking the pointer before the actor’s creation, during the actor’s lifetime, and after the actor is destroyed… always inconsistently.

I don’t even really understand what this assert is. As best as I can tell from looking at the code, it seems like someone’s passing in an invalid Index (<0) to FUObjectArray::IndexToObject(). We get there by way of IsValid() calling UObjectBaseUtility::IsPendingKill(), which does this:

return GUObjectArray.IndexToObject(InternalIndex)->IsPendingKill();

And the InternalIndex value is apparently just… bad. So we never even get to find out if our object is pending kill.

My best guess would be that the object has been fully destroyed by this point, but if that’s the case, then doesn’t it render IsValid() kind of useless?

Is there something I’m missing, here?

Digging a bit further…

The GUObjectArray.IndexToObject(InternalIndex) call has a bad value for InternalIndex, so I went searching for references to InternalIndex to see who assigns it and under what circumstances. The only place I could find was FUObjectArray::AllocateUObjectIndex() and the only code path I could see that takes us there stems from UObjectBase::AddObject.

That seems to suggest the InternalIndex is not becoming bad (e.g. upon destruction) but rather was bad from the get-go. For… some reason.

exasperated sigh

This nominally appears to be related to garbage collection. Sticking a UPROPERTY(Transient) on my pointer declaration fixes the issue.

I’ve encountered this before, with TArrays that would suddenly disappear from existence. The same fix applied there. What confuses me is how (and why) Unreal will garbage-collect things that are NOT tagged with UPROPERTY. Doesn’t that seem, like, really incredibly massively dangerous? O_o

That’s intentional, UPROPERTY is what ‘stops’ them from being garbage collected when they have references to other objects. If it doesn’t it might be cleared up, and you have to manage the lifetime of the object yourself.

This happened to me a lot when I tried to use pointers to structs. Unreal would keep collecting it. In the end I had to make the struct a UObject.

See, that’s the weird part, to me. I’ve seen that kind of phrasing in related documentation before, but if Unreal is garbage-collecting all my non-UPROPERTY stuff then I can’t manage the lifetime of the object myself, because Unreal is making those decisions for me.

I mean, it works the way that it works, and that’s whatever, but it just seems so bizarre and counterintuitive that it works this way.

I might have messed up my wording there…

Basically as I understand it - if you create an object using NewObject, and you don’t reference it anywhere with a UPROPERTY, then it will get deleted because Unreal will think that nothing is pointing to it, and it’s just floating about. If no object holds a strong reference to a UObject then it’ll be nuked.

Ah, okay, that actually does make more sense, since I am using SpawnActor() in this case. So allocating anything UObject-like to a bare pointer seems to be a general no-no and risks unwanted garbage collection, but allocating other kinds of things (my own non-UObject classes, arbitrary blocks of memory, whatever) to a bare pointer should be safe from the GC since Unreal wouldn’t know about that stuff anyway.

Granted, I don’t have a need to allocate non-UObject stuff, so this is kinda academic… just wrapping my mind around it.