Files
dave jones2 feaebd2165 UE-219092 - Warning: Flushing package <path> while it's being preloaded in the same callstack is not possible
Cyclic dependencies between Blueprints that also include a UDS in the mix can create a scenario where we might recursively load a package that we're already loading. Currently, Zen loader warns about this. While technically harmless, it makes the warning somewhat useless if it raises false positives.

The core issue is that preloading a UDS will use FObjectPropertyBase::FindImportedObject to resolve a string that represents a UObject path. In the case of a cycle, RequestPlaceholderValue creates a placeholder object, but it neglects to supply a package index for the import. As a result, ResolveDependencyPlaceholder forces a StaticLoadObject call that triggers the warning. This can be fixed by determining the package index of the corresponding object path in RequestPlaceholderValue, which avoids trying to find the import using StaticLoadObject.

This fix introduces two new API functions:
* FLinkerLoad::FindImport overload that takes a full object path and returns the corresponding package index.
* FPackageName::SplitFullObjectPath overload that returns each subobject in a path.

For this fix, the subobject support isn't necessary. However, FindImport's implementation would be incomplete if it didn't support subobjects. New tests for each function were also added to LowLevelTests. In order to test FLinkerLoad, we also need to compile out the "final" when running the tests. This makes it easier to setup a test harness for strictly testing FindImport functionality.

#jira UE-219092
#rb danny.couture, Francis.Hurteau
#rnx

[CL 35192522 by dave jones2 in ue5-main branch]
2024-07-30 14:50:16 -04:00

143 lines
5.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreTypes.h"
#include "Misc/PackageName.h"
#include "TestHarness.h"
TEST_CASE("Split full object path with subobjects", "[CoreUObject][FPackageName::SplitFullObjectPath]")
{
const FStringView ExpectedClassPath = TEXT("/Script/SomePackage.SomeClass");
const FStringView ExpectedPackagePath = TEXT("/Path/To/A/Package");
const FStringView ExpectedObjectName = TEXT("Object");
const FStringView ExpectedSubobject1Name = TEXT("Subobject1");
const FStringView ExpectedSubobject2Name = TEXT("Subobject2");
// Good cases
const FStringView TestSingleSubobject = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object:Subobject1");
const FStringView TestTwoSubobjects = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object:Subobject1.Subobject2");
const FStringView TestNoSubobjects = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object");
const FStringView TestTwoSubobjectsAndNoClassPath = TEXT("/Path/To/A/Package.Object:Subobject1.Subobject2");
const FStringView TestPackage = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package");
// Suspicious cases
const FStringView TestMissingSubobject = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object:");
const FStringView TestMissingSubobjectWithTrailingDot = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object:.");
const FStringView TestValidSubobjectWithTrailingDot = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object:Subobject1.");
const FStringView TestTwoValidSubobjectsWithTrailingDot = TEXT("/Script/SomePackage.SomeClass /Path/To/A/Package.Object:Subobject1.Subobject2.");
FStringView ClassPath;
FStringView PackagePath;
FStringView ObjectName;
TArray<FStringView> SubobjectNames;
SECTION("Single subobject verification")
{
FPackageName::SplitFullObjectPath(TestSingleSubobject, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 1);
REQUIRE(SubobjectNames[0] == ExpectedSubobject1Name);
}
SECTION("Two subobjects verification")
{
FPackageName::SplitFullObjectPath(TestTwoSubobjects, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 2);
REQUIRE(SubobjectNames[0] == ExpectedSubobject1Name);
REQUIRE(SubobjectNames[1] == ExpectedSubobject2Name);
}
SECTION("No subobjects verification")
{
FPackageName::SplitFullObjectPath(TestNoSubobjects, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 0);
}
SECTION("No class path verification (bDetectClassName on)")
{
FPackageName::SplitFullObjectPath(TestTwoSubobjectsAndNoClassPath, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath.Len() == 0);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 2);
REQUIRE(SubobjectNames[0] == ExpectedSubobject1Name);
REQUIRE(SubobjectNames[1] == ExpectedSubobject2Name);
}
SECTION("No class path verification (bDetectClassName off)")
{
FPackageName::SplitFullObjectPath(TestTwoSubobjectsAndNoClassPath, ClassPath, PackagePath, ObjectName, SubobjectNames, false);
REQUIRE(ClassPath.Len() == 0);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 2);
REQUIRE(SubobjectNames[0] == ExpectedSubobject1Name);
REQUIRE(SubobjectNames[1] == ExpectedSubobject2Name);
}
SECTION("Package verification")
{
FPackageName::SplitFullObjectPath(TestPackage, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == TEXT(""));
REQUIRE(SubobjectNames.Num() == 0);
}
SECTION("Missing subobject name yields empty subobjects array")
{
FPackageName::SplitFullObjectPath(TestMissingSubobject, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 0);
}
SECTION("Missing subobject name with trailing dot yields empty subobjects array")
{
FPackageName::SplitFullObjectPath(TestMissingSubobjectWithTrailingDot, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 0);
}
SECTION("Valid subobject with trailing dot still reports correct subobject name")
{
FPackageName::SplitFullObjectPath(TestValidSubobjectWithTrailingDot, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 1);
REQUIRE(SubobjectNames[0] == ExpectedSubobject1Name);
}
SECTION("Two valid subobjects with trailing dot still reports correct subobject names")
{
FPackageName::SplitFullObjectPath(TestTwoValidSubobjectsWithTrailingDot, ClassPath, PackagePath, ObjectName, SubobjectNames);
REQUIRE(ClassPath == ExpectedClassPath);
REQUIRE(PackagePath == ExpectedPackagePath);
REQUIRE(ObjectName == ExpectedObjectName);
REQUIRE(SubobjectNames.Num() == 2);
REQUIRE(SubobjectNames[0] == ExpectedSubobject1Name);
REQUIRE(SubobjectNames[1] == ExpectedSubobject2Name);
}
}