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)."
// 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
[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
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-141815 in the post.
0 |
Component | UE - Foundation |
---|---|
Affects Versions | 5.0 |
Target Fix | 5.0 |
Created | Feb 8, 2022 |
---|---|
Resolved | Feb 21, 2022 |
Updated | Feb 22, 2022 |