Skip to content

Commit baa27bc

Browse files
author
zhiruili
committed
fix(unreal): guard TS blueprint gen against missing GeneratedClass
LoadOrCreate used to succeed when a stale .uasset lost its parent class, leaving GeneratedClass null; CodeAnalyze then called AddFunction and crashed the editor. Bail out safely, recreate under a live TS parent when possible, and check LoadOrCreate return values on the JS side.
1 parent 5fc9dae commit baa27bc

4 files changed

Lines changed: 211 additions & 41 deletions

File tree

‎unreal/Puerts/Content/JavaScript/PuertsEditor/CodeAnalyze.js‎

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎unreal/Puerts/PuertsEditor/CodeAnalyze.ts‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,15 @@ function watch(configFilePath:string) {
704704
let moduleFileName = sourceFileName.substr(options.outDir.length + 1);
705705
let modulePath = tsi.getDirectoryPath(moduleFileName);
706706
let bp = new UE.PEBlueprintAsset();
707-
bp.LoadOrCreate(type.getSymbol().getName(), modulePath, baseTypeUClass as UE.Class, 0, 0);
707+
if (!bp.LoadOrCreate(type.getSymbol().getName(), modulePath, baseTypeUClass as UE.Class, 0, 0)) {
708+
console.error(`[CodeAnalyze] can not load or create blueprint for [${type.symbol.getName()}], skip using it as a base class`);
709+
return undefined;
710+
}
708711
bp.Save();
712+
if (!bp.GeneratedClass) {
713+
console.error(`[CodeAnalyze] blueprint of [${type.symbol.getName()}] has no generated class after saving, skip using it as a base class`);
714+
return undefined;
715+
}
709716
return bp.GeneratedClass;
710717
}
711718
}
@@ -937,7 +944,11 @@ function watch(configFilePath:string) {
937944
console.log(`gen blueprint for ${type.getSymbol().getName()}, path: ${modulePath}`);
938945
let lsFunctionLibrary:boolean = baseTypeUClass && baseTypeUClass.GetName() === "BlueprintFunctionLibrary";
939946
let bp = new UE.PEBlueprintAsset();
940-
bp.LoadOrCreateWithMetaData(type.getSymbol().getName(), modulePath, baseTypeUClass, 0, 0, uemeta.compileClassMetaData(type));
947+
if (!bp.LoadOrCreateWithMetaData(type.getSymbol().getName(), modulePath, baseTypeUClass, 0, 0, uemeta.compileClassMetaData(type))) {
948+
// Continuing would call AddFunction/AddMemberVariable against a null GeneratedClass and crash the editor.
949+
console.error(`[CodeAnalyze] can not load or create blueprint for [${type.getSymbol().getName()}], skip generating it`);
950+
return;
951+
}
941952
let hasConstructor = false;
942953
let properties: ts.Symbol[] = [];
943954
type.symbol.valueDeclaration.forEachChild(x => {

‎unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cpp‎

Lines changed: 178 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static bool IsPlaying()
5858
if (IsPlaying()) \
5959
{ \
6060
UE_LOG(PuertsEditorModule, Error, TEXT("change the layout of class[%s] in PIE mode is forbiden!"), \
61-
*GeneratedClass->GetName()); \
61+
*GetNameSafe(GeneratedClass)); \
6262
NeedSave = false; \
6363
return false; \
6464
}
@@ -67,11 +67,26 @@ static bool IsPlaying()
6767
if (IsPlaying()) \
6868
{ \
6969
UE_LOG(PuertsEditorModule, Error, TEXT("change the layout of class[%s] in PIE mode is forbiden!"), \
70-
*GeneratedClass->GetName()); \
70+
*GetNameSafe(GeneratedClass)); \
7171
NeedSave = false; \
7272
return; \
7373
}
7474

75+
bool UPEBlueprintAsset::IsAssetReady(const TCHAR* InOperation)
76+
{
77+
if (IsValid(Blueprint) && IsValid(GeneratedClass))
78+
{
79+
return true;
80+
}
81+
82+
// LoadOrCreate[WithMetaData] failed (or was refused in PIE mode) and left this asset half initialized. The
83+
// TypeScript side drives every Add*/Setup* call unconditionally, so bail out instead of dereferencing null.
84+
UE_LOG(PuertsEditorModule, Error, TEXT("%s skipped: blueprint asset is not ready (blueprint[%s], generated class[%s])"),
85+
InOperation, *GetNameSafe(Blueprint), *GetNameSafe(GeneratedClass));
86+
NeedSave = false;
87+
return false;
88+
}
89+
7590
bool UPEBlueprintAsset::Existed(const FString& InName, const FString& InPath)
7691
{
7792
FString BPPath = FString(TEXT(TS_BLUEPRINT_PATH)) / InName + TEXT(".uasset");
@@ -95,22 +110,87 @@ bool UPEBlueprintAsset::LoadOrCreate(
95110
if (Blueprint)
96111
{
97112
GeneratedClass = Blueprint->GeneratedClass;
98-
if (auto TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(GeneratedClass))
113+
if (IsValid(GeneratedClass))
99114
{
100-
HasConstructor = TypeScriptGeneratedClass->HasConstructor;
115+
if (auto TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(GeneratedClass))
116+
{
117+
HasConstructor = TypeScriptGeneratedClass->HasConstructor;
118+
}
119+
Package = Cast<UPackage>(Blueprint->GetOuter());
120+
if (Blueprint->ParentClass != ParentClass)
121+
{
122+
// Normal TypeScript reparent: old parent still loads, just retarget and let Save recompile.
123+
CanChangeCheckWithBoolRet();
124+
Blueprint->ParentClass = ParentClass;
125+
NeedSave = true;
126+
}
127+
else
128+
{
129+
NeedSave = false;
130+
}
131+
return true;
101132
}
102-
Package = Cast<UPackage>(Blueprint->GetOuter());
103-
if (Blueprint->ParentClass != ParentClass)
133+
134+
// Stale asset: the parent class recorded in the .uasset failed to load, so GeneratedClass never came up
135+
// (see the LogLinker "both will fail to load" warnings). If TypeScript already points at a live ParentClass,
136+
// tear the broken asset down and fall through to recreate - otherwise users could not recover by changing
137+
// the TS base class. With no replacement parent there is nothing we can safely do.
138+
if (!IsValid(ParentClass))
104139
{
105-
CanChangeCheckWithBoolRet();
106-
Blueprint->ParentClass = ParentClass;
107-
NeedSave = true;
140+
UE_LOG(PuertsEditorModule, Error,
141+
TEXT("blueprint [%s] has no valid generated class and no replacement parent was provided, skip"), *PackageName);
142+
Blueprint = nullptr;
143+
GeneratedClass = nullptr;
144+
NeedSave = false;
145+
return false;
108146
}
109-
else
147+
148+
if (IsPlaying())
110149
{
150+
UE_LOG(PuertsEditorModule, Error,
151+
TEXT("blueprint [%s] is broken (missing generated class) but recreating in PIE mode is forbiden!"), *PackageName);
152+
Blueprint = nullptr;
153+
GeneratedClass = nullptr;
111154
NeedSave = false;
155+
return false;
112156
}
113-
return true;
157+
158+
UE_LOG(PuertsEditorModule, Warning,
159+
TEXT("blueprint [%s] has no valid generated class (recorded parent failed to load). Recreating under parent [%s]"),
160+
*PackageName, *ParentClass->GetName());
161+
162+
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
163+
AssetRegistryModule.Get().AssetDeleted(Blueprint);
164+
165+
// Move the broken object out of the package so CreateBlueprint can reuse the same asset name.
166+
const FString TrashName = InName + TEXT("__Broken_") + FGuid::NewGuid().ToString(EGuidFormats::Digits);
167+
Blueprint->ClearFlags(RF_Standalone | RF_Public);
168+
Blueprint->Rename(*TrashName, GetTransientPackage(),
169+
REN_DontCreateRedirectors | REN_DoNotDirty | REN_ForceNoResetLoaders | REN_NonTransactional);
170+
Blueprint->RemoveFromRoot();
171+
#if ENGINE_MAJOR_VERSION >= 5
172+
Blueprint->MarkAsGarbage();
173+
#else
174+
Blueprint->MarkPendingKill();
175+
#endif
176+
Blueprint = nullptr;
177+
GeneratedClass = nullptr;
178+
Package = nullptr;
179+
180+
FString AssetFilePath = FString(TEXT(TS_BLUEPRINT_PATH)) / InPath / InName + TEXT(".uasset");
181+
if (AssetFilePath[0] == TEXT('/') || AssetFilePath[0] == TEXT('\\'))
182+
{
183+
AssetFilePath = AssetFilePath.Mid(1);
184+
}
185+
AssetFilePath = FPaths::ProjectContentDir() / AssetFilePath;
186+
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
187+
if (PlatformFile.FileExists(*AssetFilePath) && !PlatformFile.DeleteFile(*AssetFilePath))
188+
{
189+
UE_LOG(PuertsEditorModule, Error, TEXT("Failed to delete broken TypeScript blueprint asset: %s"), *AssetFilePath);
190+
NeedSave = false;
191+
return false;
192+
}
193+
// Fall through to the create path below with the caller's ParentClass.
114194
}
115195

116196
if (!ParentClass)
@@ -161,6 +241,14 @@ bool UPEBlueprintAsset::LoadOrCreate(
161241
// Mark the package dirty...
162242
Package->MarkPackageDirty();
163243
GeneratedClass = Blueprint->GeneratedClass;
244+
if (!IsValid(GeneratedClass))
245+
{
246+
UE_LOG(PuertsEditorModule, Error, TEXT("blueprint [%s] was created without a valid generated class"), *PackageName);
247+
Blueprint = nullptr;
248+
GeneratedClass = nullptr;
249+
NeedSave = false;
250+
return false;
251+
}
164252
return true;
165253
}
166254
else
@@ -394,6 +482,12 @@ UClass* const GetOverrideFunctionClass(UBlueprint* Blueprint, const FName FuncNa
394482
void UPEBlueprintAsset::AddFunction(FName InName, bool IsVoid, FPEGraphPinType InGraphPinType, FPEGraphTerminalType InPinValueType,
395483
int32 InSetFlags, int32 InClearFlags)
396484
{
485+
if (!IsAssetReady(TEXT("AddFunction")))
486+
{
487+
ClearParameter();
488+
return;
489+
}
490+
397491
InSetFlags &= ~InClearFlags;
398492
InSetFlags &= ~FUNC_Native;
399493
const int32 NetMask = FUNC_Net | FUNC_NetMulticast | FUNC_NetServer | FUNC_NetClient | FUNC_NetReliable;
@@ -404,7 +498,7 @@ void UPEBlueprintAsset::AddFunction(FName InName, bool IsVoid, FPEGraphPinType I
404498

405499
UClass* SuperClass = GeneratedClass->GetSuperClass();
406500

407-
UFunction* ParentFunction = SuperClass->FindFunctionByName(InName);
501+
UFunction* ParentFunction = SuperClass ? SuperClass->FindFunctionByName(InName) : nullptr;
408502

409503
UFunction* Function = GeneratedClass->FindFunctionByName(InName, EIncludeSuperFlag::ExcludeSuper);
410504

@@ -856,6 +950,12 @@ void UPEBlueprintAsset::AddFunctionWithMetaData(FName InName, bool IsVoid, FPEGr
856950
* @brief
857951
* function body
858952
*/
953+
if (!IsAssetReady(TEXT("AddFunctionWithMetaData")))
954+
{
955+
ClearParameter();
956+
return;
957+
}
958+
859959
if (IsValid(InMetaData))
860960
{
861961
InSetFlags |= static_cast<int32>(InMetaData->FunctionFlags);
@@ -895,6 +995,11 @@ void UPEBlueprintAsset::ClearParameter()
895995

896996
void UPEBlueprintAsset::RemoveComponent(FName ComponentName)
897997
{
998+
if (!IsValid(Blueprint) || !Blueprint->SimpleConstructionScript)
999+
{
1000+
return;
1001+
}
1002+
8981003
auto SCS_Node = Blueprint->SimpleConstructionScript->FindSCSNode(ComponentName);
8991004
if (SCS_Node)
9001005
{
@@ -970,6 +1075,11 @@ void UPEBlueprintAsset::RemoveComponent(FName ComponentName)
9701075

9711076
void UPEBlueprintAsset::SetupAttachment(FName InComponentName, FName InParentComponentName)
9721077
{
1078+
if (!IsValid(Blueprint))
1079+
{
1080+
return;
1081+
}
1082+
9731083
if (Blueprint->SimpleConstructionScript)
9741084
{
9751085
auto SCS_Node = Blueprint->SimpleConstructionScript->FindSCSNode(InComponentName);
@@ -1017,6 +1127,11 @@ void UPEBlueprintAsset::SetupAttachment(FName InComponentName, FName InParentCom
10171127

10181128
void UPEBlueprintAsset::SetupAttachments(TMap<FName, FName> InAttachments)
10191129
{
1130+
if (!IsAssetReady(TEXT("SetupAttachments")))
1131+
{
1132+
return;
1133+
}
1134+
10201135
if (Blueprint->SimpleConstructionScript)
10211136
{
10221137
for (auto& KV : InAttachments)
@@ -1050,14 +1165,19 @@ void UPEBlueprintAsset::SetupAttachments(TMap<FName, FName> InAttachments)
10501165
void UPEBlueprintAsset::AddMemberVariable(FName NewVarName, FPEGraphPinType InGraphPinType, FPEGraphTerminalType InPinValueType,
10511166
int32 InLFlags, int32 InHFlags, int32 InLifetimeCondition)
10521167
{
1168+
if (!IsAssetReady(TEXT("AddMemberVariable")))
1169+
{
1170+
return;
1171+
}
1172+
10531173
uint64 InFlags = (uint64) InHFlags << 32 | InLFlags;
10541174
FEdGraphPinType PinType = ToFEdGraphPinType(InGraphPinType, InPinValueType);
10551175

10561176
if (PinType.ContainerType == EPinContainerType::None)
10571177
{
10581178
if (auto ComponentClass = Cast<UClass>(PinType.PinSubCategoryObject))
10591179
{
1060-
if (Blueprint->GeneratedClass->IsChildOf<AActor>() && Blueprint->SimpleConstructionScript &&
1180+
if (GeneratedClass->IsChildOf<AActor>() && Blueprint->SimpleConstructionScript &&
10611181
PinType.PinCategory == UEdGraphSchema_K2::PC_Object &&
10621182
(ComponentClass == UActorComponent::StaticClass() || ComponentClass->IsChildOf<UActorComponent>()))
10631183
{
@@ -1183,6 +1303,11 @@ void UPEBlueprintAsset::AddMemberVariable(FName NewVarName, FPEGraphPinType InGr
11831303
void UPEBlueprintAsset::AddMemberVariableWithMetaData(FName InNewVarName, FPEGraphPinType InGraphPinType,
11841304
FPEGraphTerminalType InPinValueType, int32 InLFlags, int32 InHFLags, int32 InLifetimeCondition, UPEPropertyMetaData* InMetaData)
11851305
{
1306+
if (!IsAssetReady(TEXT("AddMemberVariableWithMetaData")))
1307+
{
1308+
return;
1309+
}
1310+
11861311
if (IsValid(InMetaData))
11871312
{ // handle the conflict here
11881313
EPropertyFlags InputFlags = static_cast<EPropertyFlags>((static_cast<uint64>(InHFLags) << 32) + InLFlags);
@@ -1356,38 +1481,54 @@ void UPEBlueprintAsset::Save()
13561481
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
13571482
FKismetEditorUtilities::CompileBlueprint(Blueprint);
13581483

1359-
for (TFieldIterator<UFunction> FuncIt(TypeScriptGeneratedClass, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
1484+
// Compiling may reinstance the class: the previous UClass is trashed while the blueprint points at a
1485+
// freshly created one. Refresh the cached pointer, otherwise derived TypeScript classes that take
1486+
// GeneratedClass as their parent keep a dead class.
1487+
GeneratedClass = Blueprint->GeneratedClass;
1488+
TypeScriptGeneratedClass = Cast<UTypeScriptGeneratedClass>(GeneratedClass);
1489+
1490+
if (IsValid(TypeScriptGeneratedClass))
13601491
{
1361-
auto Function = *FuncIt;
1362-
Function->FunctionFlags &= ~FUNC_Native;
1492+
for (TFieldIterator<UFunction> FuncIt(TypeScriptGeneratedClass, EFieldIteratorFlags::ExcludeSuper); FuncIt;
1493+
++FuncIt)
1494+
{
1495+
auto Function = *FuncIt;
1496+
Function->FunctionFlags &= ~FUNC_Native;
13631497

1364-
auto FunctionFName = Function->GetFName();
1365-
FString FunctionName = Function->GetName();
1498+
auto FunctionFName = Function->GetFName();
1499+
FString FunctionName = Function->GetName();
13661500

1367-
static FString AxisPrefix(TEXT("InpAxisEvt_"));
1368-
if (FunctionName.StartsWith(AxisPrefix))
1369-
{
1370-
auto FunctionNameWithoutPrefix = FunctionName.Mid(AxisPrefix.Len());
1371-
int32 SubPos;
1372-
if (FunctionNameWithoutPrefix.FindChar('_', SubPos))
1501+
static FString AxisPrefix(TEXT("InpAxisEvt_"));
1502+
if (FunctionName.StartsWith(AxisPrefix))
13731503
{
1374-
FunctionName = FunctionNameWithoutPrefix.Mid(0, SubPos);
1504+
auto FunctionNameWithoutPrefix = FunctionName.Mid(AxisPrefix.Len());
1505+
int32 SubPos;
1506+
if (FunctionNameWithoutPrefix.FindChar('_', SubPos))
1507+
{
1508+
FunctionName = FunctionNameWithoutPrefix.Mid(0, SubPos);
1509+
}
13751510
}
1376-
}
1377-
static FString ActionPrefix(TEXT("InpActEvt_"));
1378-
if (FunctionName.StartsWith(ActionPrefix))
1379-
{
1380-
auto FunctionNameWithoutPrefix = FunctionName.Mid(ActionPrefix.Len());
1381-
int32 SubPos;
1382-
if (FunctionNameWithoutPrefix.FindChar('_', SubPos))
1511+
static FString ActionPrefix(TEXT("InpActEvt_"));
1512+
if (FunctionName.StartsWith(ActionPrefix))
13831513
{
1384-
FunctionName = FunctionNameWithoutPrefix.Mid(0, SubPos);
1514+
auto FunctionNameWithoutPrefix = FunctionName.Mid(ActionPrefix.Len());
1515+
int32 SubPos;
1516+
if (FunctionNameWithoutPrefix.FindChar('_', SubPos))
1517+
{
1518+
FunctionName = FunctionNameWithoutPrefix.Mid(0, SubPos);
1519+
}
1520+
}
1521+
if (FunctionAdded.Contains(*FunctionName))
1522+
{
1523+
TypeScriptGeneratedClass->FunctionToRedirect.Add(FunctionFName);
13851524
}
13861525
}
1387-
if (FunctionAdded.Contains(*FunctionName))
1388-
{
1389-
TypeScriptGeneratedClass->FunctionToRedirect.Add(FunctionFName);
1390-
}
1526+
}
1527+
else
1528+
{
1529+
UE_LOG(PuertsEditorModule, Error,
1530+
TEXT("blueprint [%s] has no valid generated class after compiling, function redirection is skipped"),
1531+
*GetNameSafe(Blueprint));
13911532
}
13921533

13931534
TArray<UPackage*> PackagesToSave;

0 commit comments

Comments
 (0)