Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Switch.cpp
jordan hoffmann 0e53056bc6 Clang now optimizes away this==nullptr. Calls to IsChildOf from a null UObject pointer will cause undefined behavior. This is a retroactive attempt to pad potentially dangerous calls to IsChildOf with a null check in the following directories:
- Plugins/BlueprintContext
- Editor/GlueprintGraph
- Editor/GraphEditor
- Editor/Kismet
- Editor/KismetCompiler
- Editor/UnrealEd/Private/Kismet2

note: if you're seeing this CL in the perforce history because you're trying to figure out why there's a null check that doesn't make sense, This is why. The goal of this CL is to preserve the behavior before IsChildOf changed rather than analyze whether that behavior makes sense. Use your best judgement

#rb marc.audy
#preflight 6299023a6438e3c731307a69

[CL 20474984 by jordan hoffmann in ue5-main branch]
2022-06-02 16:09:18 -04:00

369 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_Switch.h"
#include "UObject/UnrealType.h"
#include "EdGraphSchema_K2.h"
#include "EdGraph/EdGraphNodeUtils.h"
#include "EdGraphUtilities.h"
#include "BPTerminal.h"
#include "KismetCompilerMisc.h"
#include "KismetCompiler.h"
#include "EditorCategoryUtils.h"
#include "Styling/AppStyle.h"
#define LOCTEXT_NAMESPACE "K2Node_Switch"
namespace
{
static FName DefaultPinName(TEXT("Default"));
static FName SelectionPinName(TEXT("Selection"));
}
//////////////////////////////////////////////////////////////////////////
// FKCHandler_Switch
class FKCHandler_Switch : public FNodeHandlingFunctor
{
protected:
TMap<UEdGraphNode*, FBPTerminal*> BoolTermMap;
public:
FKCHandler_Switch(FKismetCompilerContext& InCompilerContext)
: FNodeHandlingFunctor(InCompilerContext)
{
}
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
UK2Node_Switch* SwitchNode = Cast<UK2Node_Switch>(Node);
FNodeHandlingFunctor::RegisterNets(Context, Node);
// Create a term to determine if the compare was successful or not
//@TODO: Ideally we just create one ever, not one per switch
FBPTerminal* BoolTerm = Context.CreateLocalTerminal();
BoolTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Boolean;
BoolTerm->Source = Node;
BoolTerm->Name = Context.NetNameMap->MakeValidName(Node, TEXT("CmpSuccess"));
BoolTermMap.Add(Node, BoolTerm);
}
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
UK2Node_Switch* SwitchNode = CastChecked<UK2Node_Switch>(Node);
FEdGraphPinType ExpectedExecPinType;
ExpectedExecPinType.PinCategory = UEdGraphSchema_K2::PC_Exec;
// Make sure that the input pin is connected and valid for this block
UEdGraphPin* ExecTriggeringPin = Context.FindRequiredPinByName(SwitchNode, UEdGraphSchema_K2::PN_Execute, EGPD_Input);
if ((ExecTriggeringPin == NULL) || !Context.ValidatePinType(ExecTriggeringPin, ExpectedExecPinType))
{
CompilerContext.MessageLog.Error(*LOCTEXT("NoValidExecutionPinForSwitch_Error", "@@ must have a valid execution pin @@").ToString(), SwitchNode, ExecTriggeringPin);
return;
}
// Make sure that the selection pin is connected and valid for this block
UEdGraphPin* SelectionPin = SwitchNode->GetSelectionPin();
if ((SelectionPin == NULL) || !Context.ValidatePinType(SelectionPin, SwitchNode->GetPinType()))
{
CompilerContext.MessageLog.Error(*LOCTEXT("NoValidSelectionPinForSwitch_Error", "@@ must have a valid execution pin @@").ToString(), SwitchNode, SelectionPin);
return;
}
// Find the boolean intermediate result term, so we can track whether the compare was successful
FBPTerminal* BoolTerm = BoolTermMap.FindRef(SwitchNode);
// Generate the output impulse from this node
UEdGraphPin* SwitchSelectionNet = FEdGraphUtilities::GetNetFromPin(SelectionPin);
FBPTerminal* SwitchSelectionTerm = Context.NetMap.FindRef(SwitchSelectionNet);
if ((BoolTerm != NULL) && (SwitchSelectionTerm != NULL))
{
UEdGraphNode* TargetNode = NULL;
UEdGraphPin* FuncPin = SwitchNode->GetFunctionPin();
FBPTerminal* FuncContext = Context.NetMap.FindRef(FuncPin);
UEdGraphPin* DefaultPin = SwitchNode->GetDefaultPin();
// We don't need to generate if checks if there are no connections to it if there is no default pin or if the default pin is not linked
// If there is a default pin that is linked then it would fall through to that default if we do not generate the cases
const bool bCanSkipUnlinkedCase = (DefaultPin == nullptr || DefaultPin->LinkedTo.Num() == 0);
// Pull out function to use
UClass* FuncClass = Cast<UClass>(FuncPin->PinType.PinSubCategoryObject.Get());
UFunction* FunctionPtr = FindUField<UFunction>(FuncClass, FuncPin->PinName);
check(FunctionPtr);
// Run thru all the output pins except for the default label
for (auto PinIt = SwitchNode->Pins.CreateIterator(); PinIt; ++PinIt)
{
UEdGraphPin* Pin = *PinIt;
if ((Pin->Direction == EGPD_Output) && (Pin != DefaultPin) && (!bCanSkipUnlinkedCase || Pin->LinkedTo.Num() > 0))
{
// Create a term for the switch case value
FBPTerminal* CaseValueTerm = new FBPTerminal();
Context.Literals.Add(CaseValueTerm);
CaseValueTerm->Name = SwitchNode->GetExportTextForPin(Pin);
CaseValueTerm->Type = SwitchNode->GetInnerCaseType();
CaseValueTerm->SourcePin = Pin;
CaseValueTerm->bIsLiteral = true;
// Call the comparison function associated with this switch node
FBlueprintCompiledStatement& Statement = Context.AppendStatementForNode(SwitchNode);
Statement.Type = KCST_CallFunction;
Statement.FunctionToCall = FunctionPtr;
Statement.FunctionContext = FuncContext;
Statement.bIsParentContext = false;
Statement.LHS = BoolTerm;
Statement.RHS.Add(SwitchSelectionTerm);
Statement.RHS.Add(CaseValueTerm);
// Jump to output if strings are actually equal
FBlueprintCompiledStatement& IfFailTest_SucceedAtBeingEqualGoto = Context.AppendStatementForNode(SwitchNode);
IfFailTest_SucceedAtBeingEqualGoto.Type = KCST_GotoIfNot;
IfFailTest_SucceedAtBeingEqualGoto.LHS = BoolTerm;
Context.GotoFixupRequestMap.Add(&IfFailTest_SucceedAtBeingEqualGoto, Pin);
}
}
// Finally output default pin
GenerateSimpleThenGoto(Context, *SwitchNode, DefaultPin);
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("ResolveTermPassed_Error", "Failed to resolve term passed into @@").ToString(), SelectionPin);
}
}
private:
FEdGraphPinType ExpectedSelectionPinType;
};
UK2Node_Switch::UK2Node_Switch(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bHasDefaultPin = true;
bHasDefaultPinValueChanged = false;
}
void UK2Node_Switch::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (PropertyName == TEXT("bHasDefaultPin"))
{
// Signal to the reconstruction logic that the default pin value has changed
bHasDefaultPinValueChanged = true;
if (!bHasDefaultPin)
{
UEdGraphPin* DefaultPin = GetDefaultPin();
if (DefaultPin)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
K2Schema->BreakPinLinks(*DefaultPin, true);
}
}
ReconstructNode();
// Clear the default pin value change flag
bHasDefaultPinValueChanged = false;
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
FName UK2Node_Switch::GetSelectionPinName()
{
return SelectionPinName;
}
void UK2Node_Switch::AllocateDefaultPins()
{
// Add default pin
if (bHasDefaultPin)
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, DefaultPinName);
}
// Add exec input pin
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
// Create selection pin based on type
CreateSelectionPin();
// Create a new function pin
CreateFunctionPin();
// Create any case pins if required
CreateCasePins();
}
UK2Node::ERedirectType UK2Node_Switch::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
{
// If the default pin setting has changed, return a match for the "execute" input pin (which will have swapped slots), so that we don't have to break any links to it
if(bHasDefaultPinValueChanged && ((OldPinIndex == 0) || (NewPinIndex == 0)))
{
if((bHasDefaultPin && OldPinIndex == 0 && NewPinIndex == 1)
|| (!bHasDefaultPin && OldPinIndex == 1 && NewPinIndex == 0))
{
return ERedirectType_Name;
}
}
else if (NewPin->PinName == OldPin->PinName)
{
// Compare the names, case-sensitively
return ERedirectType_Name;
}
return ERedirectType_None;
}
FLinearColor UK2Node_Switch::GetNodeTitleColor() const
{
// Use yellow for now
return FLinearColor(255.0f, 255.0f, 0.0f);
}
FSlateIcon UK2Node_Switch::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Switch_16x");
return Icon;
}
void UK2Node_Switch::AddPinToSwitchNode()
{
const FName NewPinName = GetUniquePinName();
if (!NewPinName.IsNone())
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, NewPinName);
}
}
void UK2Node_Switch::RemovePinFromSwitchNode(UEdGraphPin* TargetPin)
{
// If removing the default pin, we'll need to reconstruct the node, so send a property changed event to handle that
if(bHasDefaultPin && TargetPin == GetDefaultPin())
{
FProperty* HasDefaultPinProperty = FindFProperty<FProperty>(GetClass(), "bHasDefaultPin");
if(HasDefaultPinProperty)
{
PreEditChange(HasDefaultPinProperty);
bHasDefaultPin = false;
FPropertyChangedEvent HasDefaultPinPropertyChangedEvent(HasDefaultPinProperty);
PostEditChangeProperty(HasDefaultPinPropertyChangedEvent);
}
}
else
{
RemovePin(TargetPin);
TargetPin->MarkAsGarbage();
Pins.Remove(TargetPin);
}
}
bool UK2Node_Switch::CanRemoveExecutionPin(UEdGraphPin* TargetPin) const
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Don't allow removing last pin
int32 NumExecPins = 0;
for (int32 i = 0; i < Pins.Num(); ++i)
{
UEdGraphPin* PotentialPin = Pins[i];
if (K2Schema->IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
{
NumExecPins++;
}
}
return NumExecPins > 1;
}
// Returns the exec output pin name for a given 0-based index
FName UK2Node_Switch::GetPinNameGivenIndex(int32 Index) const
{
return *FString::Printf(TEXT("%d"), Index);
}
void UK2Node_Switch::CreateFunctionPin()
{
// Set properties on the function pin
UEdGraphPin* FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, FunctionClass, FunctionName);
FunctionPin->bDefaultValueIsReadOnly = true;
FunctionPin->bNotConnectable = true;
FunctionPin->bHidden = true;
UFunction* Function = FindUField<UFunction>(FunctionClass, FunctionName);
const bool bIsStaticFunc = Function ? Function->HasAllFunctionFlags(FUNC_Static) : false;
if (bIsStaticFunc)
{
// Wire up the self to the CDO of the class if it's not us
if (UBlueprint* BP = GetBlueprint())
{
UClass* FunctionOwnerClass = Function->GetOuterUClass();
if (!BP->SkeletonGeneratedClass || !BP->SkeletonGeneratedClass->IsChildOf(FunctionOwnerClass))
{
FunctionPin->DefaultObject = FunctionOwnerClass->GetDefaultObject();
}
}
}
}
UEdGraphPin* UK2Node_Switch::GetFunctionPin() const
{
//@TODO: Should probably use a specific index, though FindPin starts at 0, so this won't *currently* conflict with user created pins
return FindPin(FunctionName);
}
UEdGraphPin* UK2Node_Switch::GetSelectionPin() const
{
//@TODO: Should probably use a specific index, though FindPin starts at 0, so this won't *currently* conflict with user created pins
return FindPin(SelectionPinName);
}
UEdGraphPin* UK2Node_Switch::GetDefaultPin() const
{
return (bHasDefaultPin)
? Pins[0]
: NULL;
}
FNodeHandlingFunctor* UK2Node_Switch::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_Switch(CompilerContext);
}
FText UK2Node_Switch::GetMenuCategory() const
{
static FNodeTextCache CachedCategory;
if (CachedCategory.IsOutOfDate(this))
{
// FText::Format() is slow, so we cache this to save on performance
CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::FlowControl, LOCTEXT("ActionMenuCategory", "Switch")), this);
}
return CachedCategory;
}
FString UK2Node_Switch::GetExportTextForPin(const UEdGraphPin* Pin) const
{
return Pin->PinName.ToString();
}
FEdGraphPinType UK2Node_Switch::GetInnerCaseType() const
{
UEdGraphPin* SelectionPin = GetSelectionPin();
if (ensure(SelectionPin))
{
return SelectionPin->PinType;
}
return FEdGraphPinType();
}
#undef LOCTEXT_NAMESPACE