Description

Not a Regression.

Tested in:
//UE5/Release-5.0 CL 18747223 Source-GitHub
//UE4/Release-4.27 CL 18319896 Binary

User Description:

"The crash test code is located at Source/HashBucketLockedBug/Private/Bug.cpp, every step in the code contain technical explanation.

After creating many objects while iterating an outer, the hash bucket of the outer is read-locked permanently, causing the ReadOnlyLock check to fail inside FHashBucket::Add, with the following fatal error:

Fatal error: [Link Removed] [Line: 104]
Trying to add TestObject /Game/TestPackage.TestPackage:LotOfObjectsInside.CrashingObject to a hash bucket that is currently being iterated over which is not allowed and may lead to undefined behavior!

Note that the object / hash bucket is not being iterated at the time of the creation of the object.

The crash must be caused because ThreadHash.ObjectOuterMap is rebuilt while iterating: the Inners variable in ForEachObjectWithOuter point to a memory location that was previously freed (the outer map is rehashed while iterating)."

Steps to Reproduce
  1. Make a new C++ project w/o starter content named LQA00404557
  2. Create a new C++ Actor named ActorBug
    1. In the Content Browser navigate to C++ Classes/LQA00404557
    2. Right click then select New C++ Class
    3. Select 'Actor' then click Next
    4. Change the name to 'ActorBug', and make sure it is set to public
    5. In Visual Studio reload all when prompted
  3. In the ActorBug.h delete everything then paste in the following code:
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ActorBug.generated.h"

UCLASS()
class UTestObject : public UObject
{
	GENERATED_BODY()
};

UCLASS()
class UObjBug : public UObject
{
	GENERATED_BODY()

public:
	UObjBug();

	UPROPERTY()
		UTestObject* TestObj1;

	UPROPERTY()
		UTestObject* TestObj2;
};

UCLASS()
class LQA00404557_API AActorBug : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AActorBug();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

private:
	UPROPERTY()
		UObjBug* TestParentObject;
};


     4. In the ActorBug.cpp delete everything then paste in the following code:

// Fill out your copyright notice in the Description page of Project Settings.


#include "ActorBug.h"

UObjBug::UObjBug()
	: TestObj1(nullptr)
	, TestObj2(nullptr)
{
}

// Sets default values
AActorBug::AActorBug()
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	TestParentObject = nullptr;
}

// Called when the game starts or when spawned
void AActorBug::BeginPlay()
{
	Super::BeginPlay();

	UPackage* const Pkg = CreatePackage(TEXT("/Game/TestPackage"));

	TestParentObject = NewObject<UObjBug>(Pkg, "TestPackage", RF_Public | RF_Standalone);

	// Create an object that will contain at least 1 object (so there is something to iterate).
	TestParentObject->TestObj1 = NewObject<UTestObject>(TestParentObject, "FailingOuter");
	UTestObject* const NewObj = NewObject<UTestObject>(TestParentObject->TestObj1, "TestObject");

	// Create another object where many objects will be created in it.
	TestParentObject->TestObj2 = NewObject<UTestObject>(TestParentObject, "NewObjects");

	// Iterate TestObj1 and create many objects containing a children in TestObj2.
	// This will cause ThreadHash.ObjectOuterMap to be rehashed (when it reaches HashSize, so 4096).
	// Elements in ObjectOuterMap will then be reallocated, causing Inners in ForEachObjectWithOuter to be invalid,
	// leading to undefined behavior (the hash bucked will not be able to be unlocked).
	ForEachObjectWithOuter(TestParentObject->TestObj1, [this](UObject* Obj)
	{
		for (SIZE_T Idx = 1; Idx <= 5000; Idx++)
		{
			UTestObject* const NewObj = NewObject<UTestObject>(TestParentObject->TestObj2, FName("DupObj", Idx));
			NewObject<UTestObject>(NewObj, "Children");
		}
	}, false);

	// Now create an object inside TestObj1: it will try to add the new object in the TestObj1 outer,
	// but the associated hash bucket of TestObj1 is permanently locked because of the bug above,
	// causing a failure when adding the new object to the hash bucket.
	NewObject<UTestObject>(TestParentObject->TestObj1, "CrashingObject");
}

// Called every frame
void AActorBug::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

5. Save in Visual Studio then Compile in Editor using the "Recompiles and Reloads C++" button in the lower right corner of the editor.
6. Drag and drop the ActorBug into the level then Play in Editor

Expected Results:
All of objects should be created without causing issues to the outer being iterated.

Actual Results:
Editor Crashes with a Fatal Error in UObjectHash.cpp Line 319

Callstack
[2022.02.08-19.53.45:495][452]LogWindows: Error: === Critical error: ===
[2022.02.08-19.53.45:495][452]LogWindows: Error: 
[2022.02.08-19.53.45:495][452]LogWindows: Error: Fatal error: [File:E:\Git\UE5Rel-18747223\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectHash.cpp] [Line: 319] 
[2022.02.08-19.53.45:495][452]LogWindows: Error: Trying to modify UObject map (FindOrAdd) that is currently being iterated. Please make sure you're not creating new UObjects or Garbage Collecting while iterating UObject hash tables.
[2022.02.08-19.53.45:495][452]LogWindows: Error: 
[2022.02.08-19.53.45:495][452]LogWindows: Error: 
[2022.02.08-19.53.45:495][452]LogWindows: Error: 
[2022.02.08-19.53.45:495][452]LogWindows: Error: 
[2022.02.08-19.53.45:495][452]LogWindows: Error: 
[2022.02.08-19.53.45:505][452]LogExit: Executing Sta

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-141815 in the post.

0
Login to Vote

By Design
ComponentUE - Foundation
Affects Versions5.0
Target Fix5.0
CreatedFeb 8, 2022
ResolvedFeb 21, 2022
UpdatedFeb 22, 2022