Custom Blueprint Crashes Editor

So to preface this, I have very little experience with C++, so it could be something super obvious that I am completely oblivious too…

I am using UE4.27

I am working on a little plugin to use in a bunch of my games, and I needed blueprint json functionality. I decided I would try doing this on my own instead of using the Epic json blueprint plugin so that my own plugin wouldn’t be dependant on another.

So to get myself started, I took some of the function code from Epic’s json plugin and pasted it into my own blueprint function library. It compiles fine, but when I use any of the function in editor via blueprints, the editor crashes.

.h File:

#pragma once

#include "CoreMinimal.h"

#include "JsonObjectWrapper.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "CE_EditorJsonFunctions.generated.h"

/**
 * 
 */
UCLASS(BlueprintType)
class CABBEAST_EDITOR_API UCE_EditorJsonFunctions : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	// Get json object from a json string
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions", meta = (WorldContext="WorldContextObject",  HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject", DisplayName="Load Json from String"))
	static UPARAM(DisplayName="Success") bool FromString(UObject* WorldContextObject, const FString& JsonString, UPARAM(DisplayName="JsonObject") FJsonObjectWrapper& OutJsonObject);

	// Create a json object from a string
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions", meta = (DisplayName = "Get Json String"))
	static UPARAM(DisplayName="Success") bool ToString(const FJsonObjectWrapper& JsonObject, UPARAM(DisplayName="String") FString& OutJsonString);

    // Get json object from external file
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions", meta = (WorldContext="WorldContextObject",  HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject", DisplayName="Load Json from File"))
	static UPARAM(DisplayName="Success") bool FromFile(UObject* WorldContextObject, const FFilePath& File, UPARAM(DisplayName="JsonObject") FJsonObjectWrapper& OutJsonObject);

	// Check if a field exists in json object
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions")
	static UPARAM(DisplayName="Success") bool HasField(const FJsonObjectWrapper& JsonObject, const FString& FieldName);

	// Get all field names in a json object
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions")
	static UPARAM(DisplayName="Success") bool GetFieldNames(const FJsonObjectWrapper& JsonObject, TArray<FString>& FieldNames);

	// Get a bool field from a json object
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions")
	static UPARAM(DisplayName="Success") bool GetBoolField(const FJsonObjectWrapper& JsonObject, const FString& FieldName, bool& FieldValue);

	// Set a bool field in a json object
	UFUNCTION(BlueprintCallable, Category="CE Editor Json Functions")
	static UPARAM(DisplayName="Success") bool SetBoolField(const FJsonObjectWrapper& JsonObject, const FString FieldName, const bool FieldValue);
	
private:
	/** FieldName only used for logging, SrcValue should be the resolved field. */
	static bool JsonFieldToProperty(const FString& FieldName, const FJsonObjectWrapper& SourceObject, FProperty* TargetProperty, void* TargetValuePtr);

	// If FieldName empty, Object created as root, or created with name "Value" otherwise. 
	static bool PropertyToJsonField(const FString& FieldName, FProperty* SourceProperty, const void* SourceValuePtr, FJsonObjectWrapper& TargetObject);
};

.cpp File:

#include "CE_EditorJsonFunctions.h"

#include "JsonObjectConverter.h"
#include "JsonObjectWrapper.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"

#define LOCTEXT_NAMESPACE "CE_EditorJsonFunctions"

bool UCE_EditorJsonFunctions::FromString(UObject* WorldContextObject, const FString& JsonString, FJsonObjectWrapper& OutJsonObject)
{
	return OutJsonObject.JsonObjectFromString(JsonString);
}

bool UCE_EditorJsonFunctions::ToString(const FJsonObjectWrapper& JsonObject, FString& OutJsonString)
{
	if (!JsonObject.JsonObjectToString(OutJsonString))
	{
		FFrame::KismetExecutionMessage(TEXT("Failed to convert JSON Object to string"), ELogVerbosity::Error);
		return false;
	}

	return true;
}

bool UCE_EditorJsonFunctions::FromFile(UObject* WorldContextObject, const FFilePath& File, FJsonObjectWrapper& OutJsonObject)
{
	if(!FPaths::FileExists(File.FilePath))
	{
		FFrame::KismetExecutionMessage(*FString::Printf(TEXT("File not found: %s"), *File.FilePath), ELogVerbosity::Error);
		return false;
	}
	
	FString JsonString;
	if(!FFileHelper::LoadFileToString(JsonString, *File.FilePath))
	{
		FFrame::KismetExecutionMessage(*FString::Printf(TEXT("Error loading file: %s"), *File.FilePath), ELogVerbosity::Error);
		return false;
	}
	
	return FromString(WorldContextObject, JsonString, OutJsonObject);
}

bool UCE_EditorJsonFunctions::HasField(const FJsonObjectWrapper& JsonObject, const FString& FieldName)
{
	return JsonObject.JsonObject->HasField(FieldName);
}

bool UCE_EditorJsonFunctions::GetFieldNames(const FJsonObjectWrapper& JsonObject, TArray<FString>& FieldNames)
{
	FieldNames.Reserve(JsonObject.JsonObject->Values.Num());
	for (const TPair<FString, TSharedPtr<FJsonValue>>& Field : JsonObject.JsonObject->Values)
	{
		FieldNames.Add(Field.Key);
	}

	return true;
}

bool UCE_EditorJsonFunctions::GetBoolField(const FJsonObjectWrapper &JsonObject, const FString &FieldName, bool& FieldValue)
{
    if (!JsonObject.JsonObject->HasField(FieldName))
	{
		return false;
	}

	FieldValue = JsonObject.JsonObject->GetBoolField(FieldName);
	return true;
}

bool UCE_EditorJsonFunctions::SetBoolField(const FJsonObjectWrapper &JsonObject, const FString FieldName, const bool FieldValue)
{
    JsonObject.JsonObject->SetBoolField(FieldName, FieldValue);
	return true;
}

bool UCE_EditorJsonFunctions::JsonFieldToProperty(
	const FString& FieldName,
	const FJsonObjectWrapper& SourceObject,
	FProperty* TargetProperty,
	void* TargetValuePtr)
{
	check(SourceObject.JsonObject.IsValid());
	check(TargetProperty && TargetValuePtr);

	// Check that field with name exists
	const TSharedPtr<FJsonValue> JsonValue = SourceObject.JsonObject->TryGetField(FieldName);
	if(!JsonValue.IsValid())
	{
		FFrame::KismetExecutionMessage(*FString::Printf(TEXT("Field '%s' was not found on the provided JSON object."), *FieldName), ELogVerbosity::Warning);
		return false;
	}
	
	return FJsonObjectConverter::JsonValueToUProperty(JsonValue, TargetProperty, TargetValuePtr);
}

bool UCE_EditorJsonFunctions::PropertyToJsonField(
	const FString& FieldName,
	FProperty* SourceProperty,
	const void* SourceValuePtr,
	FJsonObjectWrapper& TargetObject)
{
	check(SourceProperty && SourceValuePtr);

	if(!TargetObject.JsonObject.IsValid())
	{
		TargetObject.JsonObject = MakeShared<FJsonObject>();
	}

	TargetObject.JsonObject->SetField(FieldName, FJsonObjectConverter::UPropertyToJsonValue(SourceProperty, SourceValuePtr));
	return true;
}

#undef LOCTEXT_NAMESPACE

This is one of the many crash logs. For some reason, every crash created 2 crash folders, so there are the two logs:

Plugins.log (114.0 KB)
Plugins.log (77.5 KB)

Any help is massively appreciated! <3

these files are 6 minutes different from each other (there are date-time codes included on most of the lines.
the first file at “[2023.10.13-13.24.09:348]” (this is how your PC outputs UTC time codes) finishes with main reaturn 0; which is supposed to be good… even though the last line of note is “PIE:Play in editor total start time 0.1 seconds” meaning you pushed the Play button but then the program just exited, weird but supposedly “OK”

where the last time code of the first file is 24 seconds before the first time code in the 2nd log file I am presuming you relaunched the Editor immediately.

the second file you included has a crash report starting on line 719 (“.[2023.10.13-13.24.30:079]”)
that was caused by failing an Ensure so this was an intentional exit because something that was expected in this case !FindPin(FFunctionEntryHelper::GetWorldContextPinName()) would not have been available, so the program crashed gracefully instead of causing a “worse crash”

what I am guessing is that a node is doing GetWorldContext() when either it doesn’t have Scope for that context (this should have thrown a compile error), or the UWorld* doesn’t exist at this point, but that should have thrown a NULL_REFFERENCE_Exception.

the call stack that follows points to BlueprintGraph but these should include actual functionIDs but all of them are “UnknownFunction []” and the error origination in an engine cpp if you are using an Epic Games Launcher install is not helpful.

  • is this plugin being built in a clean project, or are you building it on top of an existing project? (to isolate the issue a clean project would be best)
  • can you share a graph that is calling these functions?
  • are you sure these are being called after BeginPlay() has fired? the UWorld* technically exists outside of PIE but it is a different UWorld* then the one created inside of PIE.

Thanks for the response!

So the plugin is indeed being built in a fresh project. As for when the blueprint is being called, I’m using the begin play event in the player controller like so:

So I just tried adding one of the functions to a different blueprint function library file and it worked without crashing, so the issue lies with the specific function lib file it seems?

Edit: After a little more messing around, the FromString and FromFile functions both work, but the others seem to crash the engine.

the next step would be to try to run the blueprint debugger (setting a break point on that set node coming off of the BeginPlay().

the other option is to launch with like the Visual Studio Debugger attached, and see if that is more helpful.

writing the code is the easy part, figuring out why it doesn’t work is the hard part.

Script Stack (0 frames):

Ensure condition failed: !FindPin(FFunctionEntryHelper::GetWorldContextPinName()) [File:D:/Build/++UE4/Sync/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp] [Line: 364] 

[2023.10.13-20.31.12:814][  0]LogOutputDevice: Error: Ensure condition failed: !FindPin(FFunctionEntryHelper::GetWorldContextPinName()) [File:D:/Build/++UE4/Sync/Engine/Source/Editor/BlueprintGraph/Private/K2Node_FunctionEntry.cpp] [Line: 364] 

This prevents the editor from opening when i attempt to do it from VScode. The standard editor opens fine though (but then crashes ofc)…