diff --git a/Underanalyzer/Decompiler/AST/ASTBuilder.cs b/Underanalyzer/Decompiler/AST/ASTBuilder.cs
index 3e06240..c8b5da0 100644
--- a/Underanalyzer/Decompiler/AST/ASTBuilder.cs
+++ b/Underanalyzer/Decompiler/AST/ASTBuilder.cs
@@ -9,18 +9,21 @@ This Source Code Form is subject to the terms of the Mozilla Public
namespace Underanalyzer.Decompiler.AST;
-public class ASTBuilder
+///
+/// Manages the building of a high-level AST from control flow nodes.
+///
+public class ASTBuilder(DecompileContext context)
{
///
/// The corresponding code context for this AST builder.
///
- public DecompileContext Context { get; }
+ public DecompileContext Context { get; } = context;
///
/// Reusable expression stack for instruction simulation. When non-empty after building a control flow node,
/// usually signifies data that needs to get processed by the following control flow node.
///
- internal Stack ExpressionStack { get => TopFragmentContext.ExpressionStack; }
+ internal Stack ExpressionStack { get => TopFragmentContext!.ExpressionStack; }
///
/// The index to start processing instructions for the next ControlFlow.Block we encounter.
@@ -31,17 +34,17 @@ public class ASTBuilder
///
/// List of arguments passed into a struct fragment.
///
- internal List StructArguments { get => TopFragmentContext.StructArguments; set => TopFragmentContext.StructArguments = value; }
+ internal List? StructArguments { get => TopFragmentContext!.StructArguments; set => TopFragmentContext!.StructArguments = value; }
///
/// Set of all local variables present in the current fragment.
///
- internal HashSet LocalVariableNames { get => TopFragmentContext.LocalVariableNames; }
+ internal HashSet LocalVariableNames { get => TopFragmentContext!.LocalVariableNames; }
///
/// Set of all local variables present in the current fragment.
///
- internal List LocalVariableNamesList { get => TopFragmentContext.LocalVariableNamesList; }
+ internal List LocalVariableNamesList { get => TopFragmentContext!.LocalVariableNamesList; }
///
/// The stack used to manage fragment contexts.
@@ -51,20 +54,12 @@ public class ASTBuilder
///
/// The current/top fragment context.
///
- internal ASTFragmentContext TopFragmentContext { get; private set; }
+ internal ASTFragmentContext? TopFragmentContext { get; private set; }
///
/// Current queue of switch case expressions.
///
- internal Queue SwitchCases { get; set; } = null;
-
- ///
- /// Initializes a new AST builder from the given code context.
- ///
- public ASTBuilder(DecompileContext context)
- {
- Context = context;
- }
+ internal Queue? SwitchCases { get; set; } = null;
///
/// Builds the AST for an entire code entry, starting from the root fragment node.
@@ -72,7 +67,7 @@ public ASTBuilder(DecompileContext context)
public IStatementNode Build()
{
List output = new(1);
- PushFragmentContext(Context.FragmentNodes[0]);
+ PushFragmentContext(Context.FragmentNodes![0]);
Context.FragmentNodes[0].BuildAST(this, output);
PopFragmentContext();
return output[0];
@@ -81,7 +76,7 @@ public IStatementNode Build()
///
/// Returns the control flow node following the given control flow node, in the current block.
///
- private IControlFlowNode Follow(IControlFlowNode node)
+ private static IControlFlowNode? Follow(IControlFlowNode node)
{
// Ensure we follow a linear path
if (node.Successors.Count > 1)
@@ -109,9 +104,9 @@ private IControlFlowNode Follow(IControlFlowNode node)
///
/// Builds a block starting from a control flow node, following all of its successors linearly.
///
- internal BlockNode BuildBlock(IControlFlowNode startNode)
+ internal BlockNode BuildBlock(IControlFlowNode? startNode)
{
- BlockNode block = new(TopFragmentContext);
+ BlockNode block = new(TopFragmentContext ?? throw new System.NullReferenceException());
// Advance through all successors, building out this block
var currentNode = startNode;
@@ -134,9 +129,9 @@ internal BlockNode BuildBlock(IControlFlowNode startNode)
/// Builds a block starting from a control flow node, following all of its successors linearly,
/// before stopping at the for loop incrementor of a WhileLoop control flow node.
///
- internal BlockNode BuildBlockWhile(IControlFlowNode startNode, WhileLoop whileLoop)
+ internal BlockNode BuildBlockWhile(IControlFlowNode? startNode, WhileLoop whileLoop)
{
- BlockNode block = new(TopFragmentContext);
+ BlockNode block = new(TopFragmentContext!);
// Advance through all successors, building out this block
var currentNode = startNode;
@@ -156,13 +151,13 @@ internal BlockNode BuildBlockWhile(IControlFlowNode startNode, WhileLoop whileLo
}
// List used for output of expressions, which should never have any statements
- private readonly List expressionOutput = new();
+ private readonly List expressionOutput = [];
///
/// Builds an expression (of unknown type) starting from a control flow node,
/// following all of its successors linearly.
///
- internal IExpressionNode BuildExpression(IControlFlowNode startNode, List output = null)
+ internal IExpressionNode BuildExpression(IControlFlowNode? startNode, List? output = null)
{
output ??= expressionOutput;
int stackCountBefore = ExpressionStack.Count;
@@ -200,7 +195,7 @@ internal IExpressionNode BuildExpression(IControlFlowNode startNode, List
- internal void BuildArbitrary(IControlFlowNode startNode, List output = null, int numAllowedExpressions = 0)
+ internal void BuildArbitrary(IControlFlowNode? startNode, List? output = null, int numAllowedExpressions = 0)
{
output ??= expressionOutput;
int stackCountBefore = ExpressionStack.Count;
@@ -254,7 +249,7 @@ internal void PopFragmentContext()
{
// We have leftover data on stack; this is seemingly invalid code that can't be accurately recompiled.
// Create a new warning for this fragment.
- Context.Warnings.Add(new DecompileDataLeftoverWarning(context.ExpressionStack.Count, context.CodeEntryName));
+ Context.Warnings.Add(new DecompileDataLeftoverWarning(context.ExpressionStack.Count, context.CodeEntryName ?? ""));
}
else
{
@@ -267,7 +262,7 @@ internal void PopFragmentContext()
{
if (child.FunctionName is not null)
{
- context.SubFunctionNames[child.CodeEntryName] = child.FunctionName;
+ context.SubFunctionNames[child.CodeEntryName ?? throw new DecompilerException("Missing code entry name")] = child.FunctionName;
}
}
diff --git a/Underanalyzer/Decompiler/AST/ASTCleaner.cs b/Underanalyzer/Decompiler/AST/ASTCleaner.cs
index 8e06229..4f47f14 100644
--- a/Underanalyzer/Decompiler/AST/ASTCleaner.cs
+++ b/Underanalyzer/Decompiler/AST/ASTCleaner.cs
@@ -12,22 +12,22 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Manages cleaning/postprocessing of the AST.
///
-public class ASTCleaner
+public class ASTCleaner(DecompileContext context)
{
///
/// The decompilation context this is cleaning for.
///
- public DecompileContext Context { get; }
+ public DecompileContext Context { get; } = context;
///
/// List of arguments passed into a struct fragment.
///
- internal List StructArguments { get => TopFragmentContext.StructArguments; set => TopFragmentContext.StructArguments = value; }
+ internal List? StructArguments { get => TopFragmentContext!.StructArguments; set => TopFragmentContext!.StructArguments = value; }
///
/// Set of all local variables present in the current fragment.
///
- internal HashSet LocalVariableNames { get => TopFragmentContext.LocalVariableNames; }
+ internal HashSet LocalVariableNames { get => TopFragmentContext!.LocalVariableNames; }
///
/// The stack used to manage fragment contexts.
@@ -37,7 +37,7 @@ public class ASTCleaner
///
/// The current/top fragment context.
///
- internal ASTFragmentContext TopFragmentContext { get; private set; }
+ internal ASTFragmentContext? TopFragmentContext { get; private set; }
///
/// Helper to access the global macro resolver used for resolving macro types.
@@ -47,7 +47,7 @@ public class ASTCleaner
///
/// Helper to access an ID instance and object type union, for resolving macro types.
///
- internal IMacroType MacroInstanceIdOrObjectAsset
+ internal IMacroType? MacroInstanceIdOrObjectAsset
{
get
{
@@ -63,12 +63,7 @@ internal IMacroType MacroInstanceIdOrObjectAsset
return _macroInstanceIdOrObjectAsset;
}
}
- private IMacroType _macroInstanceIdOrObjectAsset = null;
-
- public ASTCleaner(DecompileContext context)
- {
- Context = context;
- }
+ private IMacroType? _macroInstanceIdOrObjectAsset = null;
///
/// Pushes a context onto the fragment context stack.
diff --git a/Underanalyzer/Decompiler/AST/ASTFragmentContext.cs b/Underanalyzer/Decompiler/AST/ASTFragmentContext.cs
index 06a223d..7951246 100644
--- a/Underanalyzer/Decompiler/AST/ASTFragmentContext.cs
+++ b/Underanalyzer/Decompiler/AST/ASTFragmentContext.cs
@@ -22,17 +22,17 @@ public class ASTFragmentContext
///
/// The name of the code entry this fragment belongs to.
///
- public string CodeEntryName { get => Fragment.CodeEntry.Name?.Content; }
+ public string? CodeEntryName { get => Fragment.CodeEntry.Name?.Content; }
///
- /// The name of the function this fragment belongs to, or null if none.
+ /// The name of the function this fragment belongs to, or if none.
///
- public string FunctionName { get; internal set; } = null;
+ public string? FunctionName { get; internal set; } = null;
///
/// Children of this fragment, e.g. sub-functions.
///
- internal List Children { get; } = new();
+ internal List Children { get; } = [];
///
/// Current working VM expression stack.
@@ -47,39 +47,39 @@ public class ASTFragmentContext
///
/// If not null, represents the list of arguments getting passed into this fragment (which is a struct).
///
- public List StructArguments { get; internal set; } = null;
+ public List? StructArguments { get; internal set; } = null;
///
/// Function call to the parent constructor function, if this is a constructor function that inherits
- /// another constructor function, or null otherwise.
+ /// another constructor function, or otherwise.
///
- internal IExpressionNode BaseParentCall { get; set; } = null;
+ internal IExpressionNode? BaseParentCall { get; set; } = null;
///
/// Contains all local variables referenced from within this fragment.
///
- public HashSet LocalVariableNames { get; } = new();
+ public HashSet LocalVariableNames { get; } = [];
///
/// Contains all local variables referenced from within this fragment, in order of occurrence.
///
- public List LocalVariableNamesList { get; } = new();
+ public List LocalVariableNamesList { get; } = [];
///
/// Map of code entry names to function names, for all children fragments/sub-functions of this context.
///
- public Dictionary SubFunctionNames { get; } = new();
+ public Dictionary SubFunctionNames { get; } = [];
///
/// The loop surrounding the currently-building position in the AST.
///
- internal Loop SurroundingLoop { get; set; } = null;
+ internal Loop? SurroundingLoop { get; set; } = null;
///
/// Contains local variable names that should be entirely removed from the fragment.
/// (For removing compiler-generated code.)
///
- internal HashSet LocalVariablesToPurge { get; } = new();
+ internal HashSet LocalVariablesToPurge { get; } = [];
///
/// Stack of the number of statements contained in all enveloping try finally blocks.
@@ -94,12 +94,12 @@ public class ASTFragmentContext
///
/// Contains all named argument variables referenced from within this fragment.
///
- internal HashSet NamedArguments { get; set; } = new();
+ internal HashSet NamedArguments { get; set; } = [];
///
/// Lookup of argument index to argument name, for GMLv2 named arguments.
///
- private Dictionary NamedArgumentByIndex { get; set; } = new();
+ private Dictionary NamedArgumentByIndex { get; set; } = [];
internal ASTFragmentContext(Fragment fragment)
{
@@ -127,9 +127,9 @@ internal void RemoveLocal(string name)
///
/// Generates and returns the named argument name that the given index should have.
/// By default, resorts to formatting string from settings.
- /// Returns null if prior to GMLv2 (and no named argument should be used).
+ /// Returns if prior to GMLv2 (and no named argument should be used).
///
- internal string GetNamedArgumentName(DecompileContext context, int index)
+ internal string? GetNamedArgumentName(DecompileContext context, int index)
{
// GMLv2 introduced named arguments
if (!context.GMLv2)
@@ -138,13 +138,19 @@ internal string GetNamedArgumentName(DecompileContext context, int index)
}
// Look up existing name, and use that, if it exists already
- if (NamedArgumentByIndex.TryGetValue(index, out string existingName))
+ if (NamedArgumentByIndex.TryGetValue(index, out string? existingName))
{
return existingName;
}
+ string? name = null;
+
// Resolve name from registry
- string name = context.GameContext.GameSpecificRegistry.NamedArgumentResolver.ResolveArgument(CodeEntryName, index);
+ string? codeEntryName = CodeEntryName;
+ if (codeEntryName is not null)
+ {
+ name = context.GameContext.GameSpecificRegistry.NamedArgumentResolver.ResolveArgument(codeEntryName, index);
+ }
// If no name exists in the registry, auto-generate one from settings
name ??= string.Format(context.Settings.UnknownArgumentNamePattern, index);
diff --git a/Underanalyzer/Decompiler/AST/ASTPrinter.cs b/Underanalyzer/Decompiler/AST/ASTPrinter.cs
index df214bd..1c91633 100644
--- a/Underanalyzer/Decompiler/AST/ASTPrinter.cs
+++ b/Underanalyzer/Decompiler/AST/ASTPrinter.cs
@@ -14,12 +14,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Manages the printing of all AST nodes.
///
-public class ASTPrinter
+public class ASTPrinter(DecompileContext context)
{
///
/// The decompilation context this is printing for.
///
- public DecompileContext Context { get; private set; }
+ public DecompileContext Context { get; private set; } = context;
///
/// The current string output of this printer. This should be used only when the result is needed.
@@ -29,12 +29,12 @@ public class ASTPrinter
///
/// List of arguments passed into a struct fragment.
///
- internal List StructArguments { get => TopFragmentContext.StructArguments; set => TopFragmentContext.StructArguments = value; }
+ internal List? StructArguments { get => TopFragmentContext!.StructArguments; set => TopFragmentContext!.StructArguments = value; }
///
/// Set of all local variables present in the current fragment.
///
- internal HashSet LocalVariableNames { get => TopFragmentContext.LocalVariableNames; }
+ internal HashSet LocalVariableNames { get => TopFragmentContext!.LocalVariableNames; }
///
/// The stack used to manage fragment contexts.
@@ -44,7 +44,7 @@ public class ASTPrinter
///
/// The current/top fragment context.
///
- internal ASTFragmentContext TopFragmentContext { get; private set; }
+ internal ASTFragmentContext? TopFragmentContext { get; private set; }
///
/// If true, semicolon output is manually disabled.
@@ -57,20 +57,15 @@ public class ASTPrinter
internal int FirstUnprintedWarningIndex { get; private set; } = 0;
// Builder used to store resulting code
- private StringBuilder stringBuilder = new(128);
+ private readonly StringBuilder stringBuilder = new(128);
// Management of indentation level
private int indentLevel = 0;
- private List indentStrings = new(4) { "" };
+ private readonly List indentStrings = new(4) { "" };
private string indentString = "";
// Management of newline placement
private bool lineActive = false;
-
- public ASTPrinter(DecompileContext context)
- {
- Context = context;
- }
///
/// Pushes a context onto the fragment context stack.
@@ -260,14 +255,14 @@ public void CloseBlock()
///
public string LookupFunction(IGMFunction function)
{
- if (Context.GameContext.GlobalFunctions.FunctionToName.TryGetValue(function, out string name))
+ if (Context.GameContext.GlobalFunctions.FunctionToName.TryGetValue(function, out string? name))
{
// We found a global function name!
return name;
}
string funcName = function.Name.Content;
- if (TopFragmentContext.SubFunctionNames.TryGetValue(funcName, out string realName))
+ if (TopFragmentContext!.SubFunctionNames.TryGetValue(funcName, out string? realName))
{
// We found a sub-function name within this fragment!
return realName;
diff --git a/Underanalyzer/Decompiler/AST/BlockSimulator.cs b/Underanalyzer/Decompiler/AST/BlockSimulator.cs
index d8eaadc..beb1b41 100644
--- a/Underanalyzer/Decompiler/AST/BlockSimulator.cs
+++ b/Underanalyzer/Decompiler/AST/BlockSimulator.cs
@@ -16,7 +16,7 @@ namespace Underanalyzer.Decompiler.AST;
///
internal class BlockSimulator
{
- private static readonly Dictionary DataTypeToSize = new();
+ private static readonly Dictionary DataTypeToSize = [];
///
/// Initializes precomputed data for VM simulation.
@@ -27,8 +27,9 @@ static BlockSimulator()
Type typeDataType = typeof(DataType);
foreach (DataType dataType in Enum.GetValues(typeDataType))
{
- var field = typeDataType.GetField(Enum.GetName(typeDataType, dataType));
- var info = field.GetCustomAttribute();
+ var field = typeDataType.GetField(Enum.GetName(typeDataType, dataType) ?? throw new NullReferenceException())
+ ?? throw new NullReferenceException();
+ var info = field.GetCustomAttribute() ?? throw new NullReferenceException();
DataTypeToSize[dataType] = info.Size;
}
}
@@ -161,7 +162,7 @@ private static void SimulateDuplicate(ASTBuilder builder, IGMInstruction instr)
{
// Normal duplication mode
int size = (dupSize + 1) * dupTypeSize;
- List toDuplicate = new();
+ List toDuplicate = [];
while (size > 0)
{
IExpressionNode curr = builder.ExpressionStack.Pop();
@@ -212,7 +213,7 @@ private static void SimulatePush(ASTBuilder builder, IGMInstruction instr)
}
break;
case DataType.String:
- builder.ExpressionStack.Push(new StringNode(instr.ValueString));
+ builder.ExpressionStack.Push(new StringNode(instr.ValueString ?? throw new DecompilerException("Missing string on instruction")));
break;
case DataType.Double:
builder.ExpressionStack.Push(new DoubleNode(instr.ValueDouble));
@@ -234,12 +235,12 @@ private static void SimulatePush(ASTBuilder builder, IGMInstruction instr)
///
private static void SimulatePushVariable(ASTBuilder builder, IGMInstruction instr)
{
- VariableNode variable = new(instr.Variable, instr.ReferenceVarType, instr.Kind == Opcode.Push);
+ IGMVariable gmVariable = instr.Variable ?? throw new DecompilerException("Missing variable on instruction");
// If this is a local variable, add it to the fragment context
- if (variable.Variable.InstanceType == InstanceType.Local)
+ if (gmVariable.InstanceType == InstanceType.Local)
{
- string localName = variable.Variable.Name.Content;
+ string localName = gmVariable.Name.Content;
if (builder.LocalVariableNames.Add(localName))
{
builder.LocalVariableNamesList.Add(localName);
@@ -247,37 +248,40 @@ private static void SimulatePushVariable(ASTBuilder builder, IGMInstruction inst
}
// Update left side of the variable
- if (instr.InstType == InstanceType.StackTop || variable.ReferenceType == VariableType.StackTop)
+ IExpressionNode left;
+ List? arrayIndices = null;
+ if (instr.InstType == InstanceType.StackTop || instr.ReferenceVarType == VariableType.StackTop)
{
// Left side is just on the top of the stack
- variable.Left = builder.ExpressionStack.Pop();
+ left = builder.ExpressionStack.Pop();
}
- else if (variable.ReferenceType == VariableType.Array)
+ else if (instr.ReferenceVarType == VariableType.Array)
{
// Left side comes after basic array indices
- variable.ArrayIndices = SimulateArrayIndices(builder);
- variable.Left = builder.ExpressionStack.Pop();
+ arrayIndices = SimulateArrayIndices(builder);
+ left = builder.ExpressionStack.Pop();
}
- else if (variable.ReferenceType is VariableType.MultiPush or VariableType.MultiPushPop)
+ else if (instr.ReferenceVarType is VariableType.MultiPush or VariableType.MultiPushPop)
{
// Left side comes after a single array index
- variable.ArrayIndices = new() { builder.ExpressionStack.Pop() };
- variable.Left = builder.ExpressionStack.Pop();
+ arrayIndices = [builder.ExpressionStack.Pop()];
+ left = builder.ExpressionStack.Pop();
}
else
{
// Simply use the instance type stored on the instruction as the left side
- variable.Left = new InstanceTypeNode(instr.InstType);
+ left = new InstanceTypeNode(instr.InstType);
}
// If the left side of the variable is the instance type of StackTop, then we go one level further.
// This is done in the VM for GMLv2's structs/objects, as they don't have instance IDs.
- if (variable.Left is Int16Node i16 && i16.Value == (short)InstanceType.StackTop)
+ if (left is Int16Node i16 && i16.Value == (short)InstanceType.StackTop)
{
- variable.Left = builder.ExpressionStack.Pop();
+ left = builder.ExpressionStack.Pop();
}
- builder.ExpressionStack.Push(variable);
+ builder.ExpressionStack.Push(new VariableNode(instr.Variable ?? throw new DecompilerException("Missing variable on instruction"),
+ instr.ReferenceVarType, left, arrayIndices, instr.Kind == Opcode.Push));
}
///
@@ -285,7 +289,9 @@ private static void SimulatePushVariable(ASTBuilder builder, IGMInstruction inst
///
private static void SimulatePopVariable(ASTBuilder builder, List output, IGMInstruction instr)
{
- if (instr.Variable is null)
+ IGMVariable? gmVariable = instr.Variable;
+
+ if (gmVariable is null)
{
// "Pop Swap" instruction variant - just moves stuff around on the stack
IExpressionNode e1 = builder.ExpressionStack.Pop();
@@ -311,13 +317,12 @@ private static void SimulatePopVariable(ASTBuilder builder, List
return;
}
- VariableNode variable = new(instr.Variable, instr.ReferenceVarType);
- IExpressionNode valueToAssign = null;
+ IExpressionNode? valueToAssign = null;
// If this is a local variable, add it to the fragment context
- if (variable.Variable.InstanceType == InstanceType.Local)
+ if (gmVariable.InstanceType == InstanceType.Local)
{
- string localName = variable.Variable.Name.Content;
+ string localName = gmVariable.Name.Content;
if (builder.LocalVariableNames.Add(localName))
{
builder.LocalVariableNamesList.Add(localName);
@@ -331,30 +336,35 @@ private static void SimulatePopVariable(ASTBuilder builder, List
}
// Update left side of the variable
- if (variable.ReferenceType == VariableType.StackTop)
+ IExpressionNode left;
+ List? arrayIndices = null;
+ if (instr.ReferenceVarType == VariableType.StackTop)
{
// Left side is just on the top of the stack
- variable.Left = builder.ExpressionStack.Pop();
+ left = builder.ExpressionStack.Pop();
}
- else if (variable.ReferenceType == VariableType.Array)
+ else if (instr.ReferenceVarType == VariableType.Array)
{
// Left side comes after basic array indices
- variable.ArrayIndices = SimulateArrayIndices(builder);
- variable.Left = builder.ExpressionStack.Pop();
+ arrayIndices = SimulateArrayIndices(builder);
+ left = builder.ExpressionStack.Pop();
}
else
{
// Simply use the instance type stored on the instruction as the left side
- variable.Left = new InstanceTypeNode(instr.InstType);
+ left = new InstanceTypeNode(instr.InstType);
}
// If the left side of the variable is the instance type of StackTop, then we go one level further.
// This is done in the VM for GMLv2's structs/objects, as they don't have instance IDs.
- if (variable.Left is Int16Node i16 && i16.Value == (short)InstanceType.StackTop)
+ if (left is Int16Node i16 && i16.Value == (short)InstanceType.StackTop)
{
- variable.Left = builder.ExpressionStack.Pop();
+ left = builder.ExpressionStack.Pop();
}
+ // Create actual variable node
+ VariableNode variable = new(gmVariable, instr.ReferenceVarType, left, arrayIndices);
+
// Pop value only now if first type isn't Int32
if (instr.Type1 != DataType.Int32)
{
@@ -418,7 +428,7 @@ private static void SimulatePopVariable(ASTBuilder builder, List
}
// Add statement to output list
- output.Add(new AssignNode(variable, valueToAssign));
+ output.Add(new AssignNode(variable, valueToAssign ?? throw new DecompilerException("Failed to get assignment value")));
}
///
@@ -431,7 +441,7 @@ private static List SimulateArrayIndices(ASTBuilder builder)
if (builder.Context.GMLv2)
{
// In GMLv2 and above, all basic array accesses are 1D
- return new() { index };
+ return [index];
}
// Check if this is a 2D array index
@@ -440,10 +450,10 @@ private static List SimulateArrayIndices(ASTBuilder builder)
binary2 is { Instruction.Kind: Opcode.Multiply, Right: Int32Node int32 } &&
int32.Value == VMConstants.OldArrayLimit)
{
- return new() { binary2.Left, binary.Right };
+ return [binary2.Left, binary.Right];
}
- return new() { index };
+ return [index];
}
///
@@ -452,7 +462,7 @@ private static List SimulateArrayIndices(ASTBuilder builder)
private static void SimulateCall(ASTBuilder builder, List output, IGMInstruction instr)
{
// Check if we're a special function we need to handle
- string funcName = instr.Function?.Name?.Content;
+ string? funcName = instr.Function?.Name?.Content;
if (funcName is not null)
{
switch (funcName)
@@ -470,7 +480,7 @@ private static void SimulateCall(ASTBuilder builder, List output
case VMConstants.CopyStaticFunction:
// Top of stack is function reference to base class (which we ignore), followed by parent call
builder.ExpressionStack.Pop();
- builder.TopFragmentContext.BaseParentCall = builder.ExpressionStack.Pop();
+ builder.TopFragmentContext!.BaseParentCall = builder.ExpressionStack.Pop();
return;
case VMConstants.FinishFinallyFunction:
builder.ExpressionStack.Push(new TryCatchNode.FinishFinallyNode());
@@ -490,7 +500,7 @@ private static void SimulateCall(ASTBuilder builder, List output
args.Add(builder.ExpressionStack.Pop());
}
- builder.ExpressionStack.Push(new FunctionCallNode(instr.Function, args));
+ builder.ExpressionStack.Push(new FunctionCallNode(instr.Function ?? throw new DecompilerException("Missing function on instruction"), args));
}
///
@@ -535,7 +545,7 @@ private static void SimulateCallVariable(ASTBuilder builder, IGMInstruction inst
{
// Load function/method and the instance to call it on from the stack
IExpressionNode function = builder.ExpressionStack.Pop();
- IExpressionNode instance = builder.ExpressionStack.Pop();
+ IExpressionNode? instance = builder.ExpressionStack.Pop();
// Load all arguments on stack into list
int numArgs = instr.ArgumentCount;
@@ -671,16 +681,14 @@ private static void SimulateExtended(ASTBuilder builder, List ou
private static void SimulateMultiArrayPush(ASTBuilder builder)
{
IExpressionNode index = builder.ExpressionStack.Pop();
- VariableNode variable = builder.ExpressionStack.Pop() as VariableNode;
- if (variable is null)
+ if (builder.ExpressionStack.Pop() is not VariableNode variable)
{
throw new DecompilerException("Expected variable in multi-array push");
}
// Make a copy of the variable we already have, with the new index at the end of array indices
- VariableNode extendedVariable = new(variable.Variable, variable.ReferenceType, variable.RegularPush);
- extendedVariable.Left = variable.Left;
- extendedVariable.ArrayIndices = new(variable.ArrayIndices) { index };
+ List existingArrayIndices = variable.ArrayIndices ?? throw new DecompilerException("Expected existing array indices");
+ VariableNode extendedVariable = new(variable.Variable, variable.ReferenceType, variable.Left, new(existingArrayIndices) { index }, variable.RegularPush);
builder.ExpressionStack.Push(extendedVariable);
}
@@ -690,16 +698,14 @@ private static void SimulateMultiArrayPush(ASTBuilder builder)
private static void SimulateMultiArrayPop(ASTBuilder builder, List output)
{
IExpressionNode index = builder.ExpressionStack.Pop();
- VariableNode variable = builder.ExpressionStack.Pop() as VariableNode;
- if (variable is null)
+ if (builder.ExpressionStack.Pop() is not VariableNode variable)
{
throw new DecompilerException("Expected variable in multi-array pop");
}
// Make a copy of the variable we already have, with the new index at the end of array indices
- VariableNode extendedVariable = new(variable.Variable, variable.ReferenceType, variable.RegularPush);
- extendedVariable.Left = variable.Left;
- extendedVariable.ArrayIndices = new(variable.ArrayIndices) { index };
+ List existingArrayIndices = variable.ArrayIndices ?? throw new DecompilerException("Expected existing array indices");
+ VariableNode extendedVariable = new(variable.Variable, variable.ReferenceType, variable.Left, new(existingArrayIndices) { index }, variable.RegularPush);
// Make assignment node with this variable, and the value remaining at the top of the stack
IExpressionNode value = builder.ExpressionStack.Pop();
diff --git a/Underanalyzer/Decompiler/AST/IFragmentNode.cs b/Underanalyzer/Decompiler/AST/IFragmentNode.cs
index 561e76e..614f3f3 100644
--- a/Underanalyzer/Decompiler/AST/IFragmentNode.cs
+++ b/Underanalyzer/Decompiler/AST/IFragmentNode.cs
@@ -38,7 +38,7 @@ internal static IFragmentNode Create(ASTBuilder builder, Fragment fragment)
// Ensure we have a block after this fragment, so we can determine what it is
if (fragment.Successors.Count != 1 ||
- !builder.Context.BlocksByAddress.TryGetValue(fragment.Successors[0].StartAddress, out Block followingBlock))
+ !builder.Context.BlocksByAddress!.TryGetValue(fragment.Successors[0].StartAddress, out Block? followingBlock))
{
throw new DecompilerException("Expected block after fragment");
}
@@ -79,7 +79,7 @@ internal static IFragmentNode Create(ASTBuilder builder, Fragment fragment)
}
// Check if we have a name
- string funcName = null;
+ string? funcName = null;
if (followingBlock.Instructions is
[
_, _, _, _, _,
@@ -94,7 +94,7 @@ internal static IFragmentNode Create(ASTBuilder builder, Fragment fragment)
// Build body of the function
builder.PushFragmentContext(fragment);
- builder.TopFragmentContext.FunctionName = funcName;
+ builder.TopFragmentContext!.FunctionName = funcName;
BlockNode block = builder.BuildBlock(fragment.Children[0]);
block.AddBlockLocalVarDecl(builder.Context);
builder.PopFragmentContext();
@@ -164,7 +164,7 @@ followingBlock.Instructions[7] is not
builder.PopFragmentContext();
builder.StartBlockInstructionIndex = 8;
- return new StructNode(block, builder.TopFragmentContext);
+ return new StructNode(block, builder.TopFragmentContext!);
}
else
{
@@ -172,7 +172,7 @@ followingBlock.Instructions[7] is not
// Build body
builder.PushFragmentContext(fragment);
- builder.TopFragmentContext.FunctionName = funcName;
+ builder.TopFragmentContext!.FunctionName = funcName;
BlockNode block = builder.BuildBlock(fragment.Children[0]);
block.AddBlockLocalVarDecl(builder.Context);
builder.PopFragmentContext();
@@ -192,7 +192,7 @@ followingBlock.Instructions[7] is not
builder.PopFragmentContext();
builder.StartBlockInstructionIndex = 4;
- return new FunctionDeclNode(null, true, block, builder.TopFragmentContext);
+ return new FunctionDeclNode(null, true, block, builder.TopFragmentContext!);
}
}
}
diff --git a/Underanalyzer/Decompiler/AST/IFunctionCallNode.cs b/Underanalyzer/Decompiler/AST/IFunctionCallNode.cs
index 94e0022..ae6569c 100644
--- a/Underanalyzer/Decompiler/AST/IFunctionCallNode.cs
+++ b/Underanalyzer/Decompiler/AST/IFunctionCallNode.cs
@@ -14,9 +14,9 @@ namespace Underanalyzer.Decompiler.AST;
public interface IFunctionCallNode : IConditionalValueNode, IStatementNode, IExpressionNode
{
///
- /// Name of the function being called, or null if none.
+ /// Name of the function being called, or if none.
///
- public string FunctionName { get; }
+ public string? FunctionName { get; }
///
/// List of arguments used to call the function with.
diff --git a/Underanalyzer/Decompiler/AST/IMacroResolvableNode.cs b/Underanalyzer/Decompiler/AST/IMacroResolvableNode.cs
index ad54bb3..7c89a00 100644
--- a/Underanalyzer/Decompiler/AST/IMacroResolvableNode.cs
+++ b/Underanalyzer/Decompiler/AST/IMacroResolvableNode.cs
@@ -16,7 +16,7 @@ public interface IMacroResolvableNode : IExpressionNode
{
///
/// Returns the node, but with macros resolved using the given macro type.
- /// If any modifications are made, this should return a reference; otherwise, null.
+ /// If any modifications are made, this should return a reference; otherwise, .
///
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type);
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type);
}
diff --git a/Underanalyzer/Decompiler/AST/IMacroTypeNode.cs b/Underanalyzer/Decompiler/AST/IMacroTypeNode.cs
index 22d11e0..c9cc7a0 100644
--- a/Underanalyzer/Decompiler/AST/IMacroTypeNode.cs
+++ b/Underanalyzer/Decompiler/AST/IMacroTypeNode.cs
@@ -14,7 +14,7 @@ namespace Underanalyzer.Decompiler.AST;
public interface IMacroTypeNode
{
///
- /// Returns the macro type for this node as used in an expression, or null if none exists.
+ /// Returns the macro type for this node as used in an expression, or if none exists.
///
- public IMacroType GetExpressionMacroType(ASTCleaner cleaner);
+ public IMacroType? GetExpressionMacroType(ASTCleaner cleaner);
}
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ArrayInitNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ArrayInitNode.cs
index 5cddcb2..e701b7c 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ArrayInitNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ArrayInitNode.cs
@@ -12,12 +12,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents an array literal in the AST.
///
-public class ArrayInitNode : IExpressionNode, IMacroResolvableNode, IConditionalValueNode
+public class ArrayInitNode(List elements) : IExpressionNode, IMacroResolvableNode, IConditionalValueNode
{
///
/// List of elements in this array literal.
///
- public List Elements { get; }
+ public List Elements { get; } = elements;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -26,11 +26,6 @@ public class ArrayInitNode : IExpressionNode, IMacroResolvableNode, IConditional
public string ConditionalTypeName => "ArrayInit";
public string ConditionalValue => "";
- public ArrayInitNode(List elements)
- {
- Elements = elements;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
for (int i = 0; i < Elements.Count; i++)
@@ -66,7 +61,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeArrayInit typeArrayInit)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/AssetReferenceNode.cs b/Underanalyzer/Decompiler/AST/Nodes/AssetReferenceNode.cs
index f6ac69e..5d90652 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/AssetReferenceNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/AssetReferenceNode.cs
@@ -4,22 +4,24 @@ This Source Code Form is subject to the terms of the Mozilla Public
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
+using Underanalyzer.Decompiler.GameSpecific;
+
namespace Underanalyzer.Decompiler.AST;
///
/// Represents an asset reference in the AST.
///
-public class AssetReferenceNode : IExpressionNode
+public class AssetReferenceNode(int assetId, AssetType assetType) : IExpressionNode, IConditionalValueNode
{
///
/// The ID of the asset being referenced.
///
- public int AssetId { get; }
+ public int AssetId { get; } = assetId;
///
/// The type of the asset being referenced.
///
- public AssetType AssetType { get; }
+ public AssetType AssetType { get; } = assetType;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -28,12 +30,6 @@ public class AssetReferenceNode : IExpressionNode
public string ConditionalTypeName => "AssetReference";
public string ConditionalValue => $"{AssetType}:{AssetId}";
- public AssetReferenceNode(int assetId, AssetType assetType)
- {
- AssetId = assetId;
- AssetType = assetType;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -46,7 +42,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
public void Print(ASTPrinter printer)
{
- string assetName = printer.Context.GameContext.GetAssetName(AssetType, AssetId);
+ string? assetName = printer.Context.GameContext.GetAssetName(AssetType, AssetId);
if (assetName is not null)
{
printer.Write(assetName);
@@ -65,4 +61,13 @@ public void Print(ASTPrinter printer)
}
}
}
+
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ {
+ if (type is IMacroTypeConditional conditional)
+ {
+ return conditional.Resolve(cleaner, this);
+ }
+ return null;
+ }
}
diff --git a/Underanalyzer/Decompiler/AST/Nodes/AssignNode.cs b/Underanalyzer/Decompiler/AST/Nodes/AssignNode.cs
index 09bd775..d452303 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/AssignNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/AssignNode.cs
@@ -22,7 +22,7 @@ public class AssignNode : IStatementNode, IExpressionNode, IBlockCleanupNode
///
/// The value being assigned.
///
- public IExpressionNode Value { get; private set; }
+ public IExpressionNode? Value { get; private set; }
///
/// The type of assignment being done.
@@ -32,7 +32,7 @@ public class AssignNode : IStatementNode, IExpressionNode, IBlockCleanupNode
///
/// For prefix/postfix/compound, this is the instruction used to do the operation.
///
- public IGMInstruction BinaryInstruction { get; private set; }
+ public IGMInstruction? BinaryInstruction { get; private set; }
public bool SemicolonAfter { get => true; }
public bool Duplicated { get; set; } = false;
@@ -225,11 +225,11 @@ public void Print(ASTPrinter printer)
// We're inside a struct initialization block
Variable.Print(printer);
printer.Write(": ");
- Value.Print(printer);
+ Value!.Print(printer);
}
else
{
- if (printer.TopFragmentContext.InStaticInitialization)
+ if (printer.TopFragmentContext!.InStaticInitialization)
{
// In static initialization, we prepend the "static" keyword to the assignment
printer.Write("static ");
@@ -238,20 +238,20 @@ public void Print(ASTPrinter printer)
// Normal assignment
Variable.Print(printer);
printer.Write(" = ");
- Value.Print(printer);
+ Value!.Print(printer);
}
break;
case AssignType.Prefix:
- printer.Write((BinaryInstruction.Kind == Opcode.Add) ? "++" : "--");
+ printer.Write((BinaryInstruction!.Kind == Opcode.Add) ? "++" : "--");
Variable.Print(printer);
break;
case AssignType.Postfix:
Variable.Print(printer);
- printer.Write((BinaryInstruction.Kind == Opcode.Add) ? "++" : "--");
+ printer.Write((BinaryInstruction!.Kind == Opcode.Add) ? "++" : "--");
break;
case AssignType.Compound:
Variable.Print(printer);
- printer.Write(BinaryInstruction.Kind switch
+ printer.Write(BinaryInstruction!.Kind switch
{
Opcode.Add => " += ",
Opcode.Subtract => " -= ",
@@ -263,12 +263,12 @@ public void Print(ASTPrinter printer)
Opcode.Xor => " ^= ",
_ => throw new DecompilerException("Unknown binary instruction opcode in compound assignment")
});
- Value.Print(printer);
+ Value!.Print(printer);
break;
case AssignType.NullishCoalesce:
Variable.Print(printer);
printer.Write(" ??= ");
- Value.Print(printer);
+ Value!.Print(printer);
break;
}
}
diff --git a/Underanalyzer/Decompiler/AST/Nodes/BinaryNode.cs b/Underanalyzer/Decompiler/AST/Nodes/BinaryNode.cs
index faaeff4..db86c10 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/BinaryNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/BinaryNode.cs
@@ -65,7 +65,7 @@ public BinaryNode(IExpressionNode left, IExpressionNode right, IGMInstruction in
}
}
- private int StackTypeBias(DataType type)
+ private static int StackTypeBias(DataType type)
{
return type switch
{
@@ -169,7 +169,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return Left.RequiresMultipleLines(printer) || Right.RequiresMultipleLines(printer);
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
bool didAnything = false;
diff --git a/Underanalyzer/Decompiler/AST/Nodes/BlockLocalVarDeclNode.cs b/Underanalyzer/Decompiler/AST/Nodes/BlockLocalVarDeclNode.cs
index 91e3c33..67bb9b9 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/BlockLocalVarDeclNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/BlockLocalVarDeclNode.cs
@@ -24,7 +24,7 @@ public IStatementNode Clean(ASTCleaner cleaner)
public void Print(ASTPrinter printer)
{
- List localNames = printer.TopFragmentContext.LocalVariableNamesList;
+ List localNames = printer.TopFragmentContext!.LocalVariableNamesList;
if (localNames.Count > 0)
{
printer.Write("var ");
diff --git a/Underanalyzer/Decompiler/AST/Nodes/BlockNode.cs b/Underanalyzer/Decompiler/AST/Nodes/BlockNode.cs
index 2c9d758..6394c5f 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/BlockNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/BlockNode.cs
@@ -12,7 +12,7 @@ namespace Underanalyzer.Decompiler.AST;
/// Represents a single block of code in the AST.
/// Blocks can have an arbitrary number of child nodes.
///
-public class BlockNode : IFragmentNode, IBlockCleanupNode
+public class BlockNode(ASTFragmentContext fragmentContext) : IFragmentNode, IBlockCleanupNode
{
///
/// Whether or not curly braces are required for this block.
@@ -27,17 +27,12 @@ public class BlockNode : IFragmentNode, IBlockCleanupNode
///
/// All children contained within this block.
///
- public List Children { get; internal set; } = new();
+ public List Children { get; internal set; } = [];
public bool SemicolonAfter { get => false; }
public bool EmptyLineBefore => false;
public bool EmptyLineAfter => false;
- public ASTFragmentContext FragmentContext { get; }
-
- public BlockNode(ASTFragmentContext fragmentContext)
- {
- FragmentContext = fragmentContext;
- }
+ public ASTFragmentContext FragmentContext { get; } = fragmentContext;
public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
{
@@ -67,7 +62,7 @@ private void CleanChildren(ASTCleaner cleaner)
// If there are no local variables to be declared, removes the node where they would be declared.
private void CleanBlockLocalVars(ASTCleaner cleaner)
{
- if (cleaner.TopFragmentContext.LocalVariableNamesList.Count > 0)
+ if (cleaner.TopFragmentContext!.LocalVariableNamesList.Count > 0)
{
return;
}
diff --git a/Underanalyzer/Decompiler/AST/Nodes/BooleanNode.cs b/Underanalyzer/Decompiler/AST/Nodes/BooleanNode.cs
index 4b31c17..d9f382d 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/BooleanNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/BooleanNode.cs
@@ -11,9 +11,9 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a boolean constant in the AST.
///
-public class BooleanNode : IConstantNode, IConditionalValueNode
+public class BooleanNode(bool value) : IConstantNode, IConditionalValueNode
{
- public bool Value { get; }
+ public bool Value { get; } = value;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -22,11 +22,6 @@ public class BooleanNode : IConstantNode, IConditionalValueNode
public string ConditionalTypeName => "Boolean";
public string ConditionalValue => Value ? "true" : "false";
- public BooleanNode(bool value)
- {
- Value = value;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -42,7 +37,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ConditionalNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ConditionalNode.cs
index c763d0f..cf284c2 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ConditionalNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ConditionalNode.cs
@@ -11,22 +11,23 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a conditional expression in the AST.
///
-public class ConditionalNode : IMultiExpressionNode, IMacroResolvableNode, IConditionalValueNode
+public class ConditionalNode(IExpressionNode condition, IExpressionNode trueExpr, IExpressionNode falseExpr)
+ : IMultiExpressionNode, IMacroResolvableNode, IConditionalValueNode
{
///
/// The condition of the conditional expression.
///
- public IExpressionNode Condition { get; private set; }
+ public IExpressionNode Condition { get; private set; } = condition;
///
/// The expression that is returned when the condition is true.
///
- public IExpressionNode True { get; private set; }
+ public IExpressionNode True { get; private set; } = trueExpr;
///
/// The expression that is returned when the condition is false.
///
- public IExpressionNode False { get; private set; }
+ public IExpressionNode False { get; private set; } = falseExpr;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -35,13 +36,6 @@ public class ConditionalNode : IMultiExpressionNode, IMacroResolvableNode, ICond
public string ConditionalTypeName => "Conditional";
public string ConditionalValue => ""; // TODO?
- public ConditionalNode(IExpressionNode condition, IExpressionNode trueExpr, IExpressionNode falseExpr)
- {
- Condition = condition;
- True = trueExpr;
- False = falseExpr;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
Condition = Condition.Clean(cleaner);
@@ -91,7 +85,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
False.RequiresMultipleLines(printer);
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/DoUntilLoopNode.cs b/Underanalyzer/Decompiler/AST/Nodes/DoUntilLoopNode.cs
index 6f19d31..c7afe93 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/DoUntilLoopNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/DoUntilLoopNode.cs
@@ -9,28 +9,22 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a do..until loop in the AST.
///
-public class DoUntilLoopNode : IStatementNode
+public class DoUntilLoopNode(BlockNode body, IExpressionNode condition) : IStatementNode
{
///
/// The main block of the loop.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
///
/// The condition of the loop.
///
- public IExpressionNode Condition { get; private set; }
+ public IExpressionNode Condition { get; private set; } = condition;
public bool SemicolonAfter => true;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public DoUntilLoopNode(BlockNode body, IExpressionNode condition)
- {
- Condition = condition;
- Body = body;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
ElseToContinueCleanup.Clean(cleaner, Body);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/DoubleNode.cs b/Underanalyzer/Decompiler/AST/Nodes/DoubleNode.cs
index a0878a9..2c6dfc3 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/DoubleNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/DoubleNode.cs
@@ -13,9 +13,9 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a double constant in the AST.
///
-public class DoubleNode : IConstantNode, IConditionalValueNode
+public class DoubleNode(double value) : IConstantNode, IConditionalValueNode
{
- public double Value { get; }
+ public double Value { get; } = value;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -24,14 +24,9 @@ public class DoubleNode : IConstantNode, IConditionalValueNode
public string ConditionalTypeName => "Double";
public string ConditionalValue => Value.ToString("R", CultureInfo.InvariantCulture); // TODO: maybe do full conversion here
- public DoubleNode(double value)
- {
- Value = value;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
- if (cleaner.Context.Settings.TryGetPredefinedDouble(Value, out string predefined, out bool isMultiPart))
+ if (cleaner.Context.Settings.TryGetPredefinedDouble(Value, out string? predefined, out bool isMultiPart))
{
if (isMultiPart)
{
@@ -126,7 +121,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/EnumDeclNode.cs b/Underanalyzer/Decompiler/AST/Nodes/EnumDeclNode.cs
index 6fe1cb4..1904171 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/EnumDeclNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/EnumDeclNode.cs
@@ -13,22 +13,17 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents an enum declaration node in the AST. Only appears during AST cleanup.
///
-public class EnumDeclNode : IStatementNode
+public class EnumDeclNode(GMEnum gmEnum) : IStatementNode
{
///
/// The enum being declared.
///
- public GMEnum Enum { get; }
+ public GMEnum Enum { get; } = gmEnum;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public EnumDeclNode(GMEnum gmEnum)
- {
- Enum = gmEnum;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
EmptyLineAfter = EmptyLineBefore = cleaner.Context.Settings.EmptyLineAroundEnums;
diff --git a/Underanalyzer/Decompiler/AST/Nodes/EnumValueNode.cs b/Underanalyzer/Decompiler/AST/Nodes/EnumValueNode.cs
index 96ebab7..6151129 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/EnumValueNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/EnumValueNode.cs
@@ -11,27 +11,28 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a reference to a single enum value in the AST.
///
-public class EnumValueNode : IExpressionNode, IMacroResolvableNode, IConditionalValueNode
+public class EnumValueNode(string enumName, string enumValueName, long enumValue, bool isUnknownEnum)
+ : IExpressionNode, IMacroResolvableNode, IConditionalValueNode
{
///
/// The name of the base enum type being referenced.
///
- public string EnumName { get; }
+ public string EnumName { get; } = enumName;
///
/// The name of the value on the enum being referenced.
///
- public string EnumValueName { get; }
+ public string EnumValueName { get; } = enumValueName;
///
/// The raw value of the enum value.
///
- public long EnumValue { get; }
+ public long EnumValue { get; } = enumValue;
///
/// If true, this enum value node references an unknown enum.
///
- public bool IsUnknownEnum { get; }
+ public bool IsUnknownEnum { get; } = isUnknownEnum;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -40,14 +41,6 @@ public class EnumValueNode : IExpressionNode, IMacroResolvableNode, IConditional
public string ConditionalTypeName => "EnumValue";
public string ConditionalValue => IsUnknownEnum ? EnumValue.ToString() : $"{EnumName}.{EnumValueName}";
- public EnumValueNode(string enumName, string enumValueName, long enumValue, bool isUnknownEnum)
- {
- EnumName = enumName;
- EnumValueName = enumValueName;
- EnumValue = enumValue;
- IsUnknownEnum = isUnknownEnum;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -73,7 +66,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeInt64 type64)
{
@@ -88,7 +81,7 @@ public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
// Remove declaration altogether - it's no longer referenced
cleaner.Context.NameToEnumDeclaration.Remove(EnumName);
- cleaner.Context.EnumDeclarations.Remove(cleaner.Context.UnknownEnumDeclaration);
+ cleaner.Context.EnumDeclarations.Remove(cleaner.Context.UnknownEnumDeclaration!);
cleaner.Context.UnknownEnumDeclaration = null;
}
}
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ExitNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ExitNode.cs
index e9739d9..2ce352d 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ExitNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ExitNode.cs
@@ -23,7 +23,7 @@ public IStatementNode Clean(ASTCleaner cleaner)
public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
{
// Remove duplicated finally statements
- if (cleaner.TopFragmentContext.FinallyStatementCount.Count > 0)
+ if (cleaner.TopFragmentContext!.FinallyStatementCount.Count > 0)
{
int count = 0;
foreach (int statementCount in cleaner.TopFragmentContext.FinallyStatementCount)
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ForLoopNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ForLoopNode.cs
index e97e7d8..32221b0 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ForLoopNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ForLoopNode.cs
@@ -9,44 +9,37 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a for loop in the AST.
///
-public class ForLoopNode : IStatementNode, IBlockCleanupNode
+public class ForLoopNode(IStatementNode? initializer, IExpressionNode? condition, BlockNode? incrementor, BlockNode body)
+ : IStatementNode, IBlockCleanupNode
{
///
- /// The initialization statement before the loop, or null if none.
+ /// The initialization statement before the loop, or if none.
///
- public IStatementNode Initializer { get; internal set; }
+ public IStatementNode? Initializer { get; internal set; } = initializer;
///
/// The condition of the loop.
///
- public IExpressionNode Condition { get; private set; }
+ public IExpressionNode? Condition { get; private set; } = condition;
///
/// The code executed between iterations of the loop.
///
- public BlockNode Incrementor { get; private set; }
+ public BlockNode? Incrementor { get; private set; } = incrementor;
///
/// The main block of the loop.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
public bool SemicolonAfter { get => false; }
public bool EmptyLineBefore { get; internal set; }
public bool EmptyLineAfter { get; internal set; }
- public ForLoopNode(IStatementNode initializer, IExpressionNode condition, BlockNode incrementor, BlockNode body)
- {
- Initializer = initializer;
- Condition = condition;
- Incrementor = incrementor;
- Body = body;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Initializer = Initializer?.Clean(cleaner);
- Condition = Condition.Clean(cleaner);
+ Condition = Condition!.Clean(cleaner);
Condition.Group = false;
Incrementor?.Clean(cleaner);
@@ -62,10 +55,10 @@ public IStatementNode Clean(ASTCleaner cleaner)
Condition = null;
Incrementor = null;
- if (Initializer is not BlockNode || Initializer is BlockNode block && block.Children is not [])
+ if (Initializer is not null && (Initializer is not BlockNode || Initializer is BlockNode block && block.Children is not []))
{
// Move initializer above loop
- BlockNode newBlock = new(cleaner.TopFragmentContext);
+ BlockNode newBlock = new(cleaner.TopFragmentContext!);
newBlock.Children.Add(Initializer);
newBlock.Children.Add(this);
res = newBlock;
@@ -111,7 +104,7 @@ public void Print(ASTPrinter printer)
{
Initializer?.Print(printer);
printer.Write("; ");
- Condition.Print(printer);
+ Condition!.Print(printer);
if (Incrementor is not null)
{
printer.Write("; ");
diff --git a/Underanalyzer/Decompiler/AST/Nodes/FunctionCallNode.cs b/Underanalyzer/Decompiler/AST/Nodes/FunctionCallNode.cs
index 760899e..8e39f0b 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/FunctionCallNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/FunctionCallNode.cs
@@ -12,17 +12,18 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a function call in the AST.
///
-public class FunctionCallNode : IExpressionNode, IStatementNode, IMacroTypeNode, IMacroResolvableNode, IConditionalValueNode, IFunctionCallNode
+public class FunctionCallNode(IGMFunction function, List arguments)
+ : IExpressionNode, IStatementNode, IMacroTypeNode, IMacroResolvableNode, IConditionalValueNode, IFunctionCallNode
{
///
/// The function reference being called.
///
- public IGMFunction Function { get; }
+ public IGMFunction Function { get; } = function;
///
/// Arguments being passed into the function call.
///
- public List Arguments { get; }
+ public List Arguments { get; } = arguments;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -35,12 +36,6 @@ public class FunctionCallNode : IExpressionNode, IStatementNode, IMacroTypeNode,
public string ConditionalTypeName => "FunctionCall";
public string ConditionalValue => Function.Name.Content;
- public FunctionCallNode(IGMFunction function, List arguments)
- {
- Function = function;
- Arguments = arguments;
- }
-
IExpressionNode IASTNode.Clean(ASTCleaner cleaner)
{
// Clean up all arguments
@@ -156,12 +151,12 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IMacroType GetExpressionMacroType(ASTCleaner cleaner)
+ public IMacroType? GetExpressionMacroType(ASTCleaner cleaner)
{
return cleaner.GlobalMacroResolver.ResolveReturnValueType(cleaner, Function.Name.Content);
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/FunctionDeclNode.cs b/Underanalyzer/Decompiler/AST/Nodes/FunctionDeclNode.cs
index 07c4a78..46866a4 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/FunctionDeclNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/FunctionDeclNode.cs
@@ -12,12 +12,13 @@ namespace Underanalyzer.Decompiler.AST;
///
/// A function declaration within the AST.
///
-public class FunctionDeclNode : IFragmentNode, IMultiExpressionNode, IConditionalValueNode
+public class FunctionDeclNode(string? name, bool isConstructor, BlockNode body, ASTFragmentContext fragmentContext)
+ : IFragmentNode, IMultiExpressionNode, IConditionalValueNode
{
///
- /// Name of the function, or null if anonymous.
+ /// Name of the function, or if anonymous.
///
- public string Name { get; }
+ public string? Name { get; } = name;
///
/// If true, this function is unnamed (anonymous).
@@ -27,36 +28,28 @@ public class FunctionDeclNode : IFragmentNode, IMultiExpressionNode, IConditiona
///
/// If true, this function is a constructor function.
///
- public bool IsConstructor { get; }
+ public bool IsConstructor { get; } = isConstructor;
///
/// The body of the function.
///
- public BlockNode Body { get; }
+ public BlockNode Body { get; } = body;
///
/// Mapping of argument index to default value, for a GMLv2 function declarations.
///
- internal Dictionary ArgumentDefaultValues { get; set; } = new();
+ internal Dictionary ArgumentDefaultValues { get; set; } = [];
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
public IGMInstruction.DataType StackType { get; set; } = IGMInstruction.DataType.Variable;
- public ASTFragmentContext FragmentContext { get; }
+ public ASTFragmentContext FragmentContext { get; } = fragmentContext;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
public string ConditionalTypeName => "FunctionDecl";
- public string ConditionalValue => Name;
-
- public FunctionDeclNode(string name, bool isConstructor, BlockNode body, ASTFragmentContext fragmentContext)
- {
- Name = name;
- IsConstructor = isConstructor;
- Body = body;
- FragmentContext = fragmentContext;
- }
+ public string ConditionalValue => Name ?? "";
private void CleanBody(ASTCleaner cleaner)
{
@@ -143,8 +136,12 @@ private void CleanDefaultArgumentValues(ASTCleaner cleaner)
// Successfully found a default argument assignment - store expression and move on.
// Also, process macro resolution for the default value expression, based on the argument name.
- IExpressionNode expr = assign.Value;
- string argName = Body.FragmentContext.GetNamedArgumentName(cleaner.Context, argIndex);
+ IExpressionNode? expr = assign.Value;
+ string? argName = Body.FragmentContext.GetNamedArgumentName(cleaner.Context, argIndex);
+ if (argName is null)
+ {
+ break;
+ }
cleaner.PushFragmentContext(Body.FragmentContext);
if (expr is IMacroResolvableNode valueResolvable &&
cleaner.GlobalMacroResolver.ResolveVariableType(cleaner, argName) is IMacroType variableMacroType &&
@@ -153,6 +150,11 @@ private void CleanDefaultArgumentValues(ASTCleaner cleaner)
expr = valueResolved;
}
cleaner.PopFragmentContext();
+
+ if (expr is null)
+ {
+ break;
+ }
ArgumentDefaultValues[argIndex] = expr;
lastArgumentIndex = argIndex;
@@ -195,7 +197,7 @@ public void Print(ASTPrinter printer)
for (int i = 0; i <= Body.FragmentContext.MaxReferencedArgument; i++)
{
printer.Write(Body.FragmentContext.GetNamedArgumentName(printer.Context, i));
- if (ArgumentDefaultValues.TryGetValue(i, out IExpressionNode defaultValue))
+ if (ArgumentDefaultValues.TryGetValue(i, out IExpressionNode? defaultValue))
{
printer.Write(" = ");
printer.PushFragmentContext(Body.FragmentContext);
@@ -235,7 +237,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return true;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/FunctionReferenceNode.cs b/Underanalyzer/Decompiler/AST/Nodes/FunctionReferenceNode.cs
index f7433ae..b0591bb 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/FunctionReferenceNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/FunctionReferenceNode.cs
@@ -11,12 +11,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a function reference in the AST.
///
-public class FunctionReferenceNode : IExpressionNode, IConditionalValueNode
+public class FunctionReferenceNode(IGMFunction function) : IExpressionNode, IConditionalValueNode
{
///
/// The function being referenced.
///
- public IGMFunction Function { get; }
+ public IGMFunction Function { get; } = function;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -25,11 +25,6 @@ public class FunctionReferenceNode : IExpressionNode, IConditionalValueNode
public string ConditionalTypeName => "FunctionReference";
public string ConditionalValue => Function.Name.Content;
- public FunctionReferenceNode(IGMFunction function)
- {
- Function = function;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -45,7 +40,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/IfNode.cs b/Underanalyzer/Decompiler/AST/Nodes/IfNode.cs
index 1d41b8a..a779c79 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/IfNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/IfNode.cs
@@ -9,34 +9,27 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents an if statement in the AST.
///
-public class IfNode : IStatementNode
+public class IfNode(IExpressionNode condition, BlockNode trueBlock, BlockNode? elseBlock = null) : IStatementNode
{
///
/// The condition of the if statement.
///
- public IExpressionNode Condition { get; internal set; }
+ public IExpressionNode Condition { get; internal set; } = condition;
///
/// The main (true) block of the if statement.
///
- public BlockNode TrueBlock { get; internal set; }
+ public BlockNode TrueBlock { get; internal set; } = trueBlock;
///
- /// The else (false) block of the if statement, or null if none exists.
+ /// The else (false) block of the if statement, or if none exists.
///
- public BlockNode ElseBlock { get; internal set; }
+ public BlockNode? ElseBlock { get; internal set; } = elseBlock;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public IfNode(IExpressionNode condition, BlockNode trueBlock, BlockNode elseBlock = null)
- {
- Condition = condition;
- TrueBlock = trueBlock;
- ElseBlock = elseBlock;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Condition = Condition.Clean(cleaner);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/InstanceTypeNode.cs b/Underanalyzer/Decompiler/AST/Nodes/InstanceTypeNode.cs
index f555a36..fa87fd3 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/InstanceTypeNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/InstanceTypeNode.cs
@@ -11,12 +11,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents an instance type () in the AST.
///
-public class InstanceTypeNode : IExpressionNode, IConditionalValueNode
+public class InstanceTypeNode(IGMInstruction.InstanceType instType) : IExpressionNode, IConditionalValueNode
{
///
/// The instance type for this node.
///
- public IGMInstruction.InstanceType InstanceType { get; }
+ public IGMInstruction.InstanceType InstanceType { get; } = instType;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -25,11 +25,6 @@ public class InstanceTypeNode : IExpressionNode, IConditionalValueNode
public string ConditionalTypeName => "InstanceType";
public string ConditionalValue => InstanceType.ToString();
- public InstanceTypeNode(IGMInstruction.InstanceType instType)
- {
- InstanceType = instType;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -53,7 +48,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/Int16Node.cs b/Underanalyzer/Decompiler/AST/Nodes/Int16Node.cs
index 3a35ed2..b19d56f 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/Int16Node.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/Int16Node.cs
@@ -11,15 +11,15 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a 16-bit signed integer constant in the AST.
///
-public class Int16Node : IConstantNode, IMacroResolvableNode, IConditionalValueNode
+public class Int16Node(short value, bool regularPush) : IConstantNode, IMacroResolvableNode, IConditionalValueNode
{
- public short Value { get; }
+ public short Value { get; } = value;
///
/// If true, this number was pushed with a normal Push instruction opcode,
/// rather than the usual PushImmediate.
///
- public bool RegularPush { get; }
+ public bool RegularPush { get; } = regularPush;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -28,15 +28,8 @@ public class Int16Node : IConstantNode, IMacroResolvableNode, IConditiona
public string ConditionalTypeName => "Integer";
public string ConditionalValue => Value.ToString();
- public Int16Node(short value, bool regularPush)
- {
- Value = value;
- RegularPush = regularPush;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
- // TODO: handle asset/macro types
return this;
}
@@ -50,7 +43,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeInt32 type32)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/Int32Node.cs b/Underanalyzer/Decompiler/AST/Nodes/Int32Node.cs
index e085c71..c89bc95 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/Int32Node.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/Int32Node.cs
@@ -11,9 +11,9 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a 32-bit signed integer constant in the AST.
///
-public class Int32Node : IConstantNode, IMacroResolvableNode, IConditionalValueNode
+public class Int32Node(int value) : IConstantNode, IMacroResolvableNode, IConditionalValueNode
{
- public int Value { get; }
+ public int Value { get; } = value;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -22,14 +22,8 @@ public class Int32Node : IConstantNode, IMacroResolvableNode, IConditionalV
public string ConditionalTypeName => "Integer";
public string ConditionalValue => Value.ToString();
- public Int32Node(int value)
- {
- Value = value;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
- // TODO: macro (probably not asset) types
return this;
}
@@ -43,7 +37,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeInt32 type32)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/Int64Node.cs b/Underanalyzer/Decompiler/AST/Nodes/Int64Node.cs
index 96b91ba..c0938cf 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/Int64Node.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/Int64Node.cs
@@ -11,9 +11,9 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a 64-bit signed integer constant in the AST.
///
-public class Int64Node : IConstantNode, IMacroResolvableNode, IConditionalValueNode
+public class Int64Node(long value) : IConstantNode, IMacroResolvableNode, IConditionalValueNode
{
- public long Value { get; }
+ public long Value { get; } = value;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -22,11 +22,6 @@ public class Int64Node : IConstantNode, IMacroResolvableNode, IConditional
public string ConditionalTypeName => "Integer";
public string ConditionalValue => Value.ToString();
- public Int64Node(long value)
- {
- Value = value;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
// If we aren't detected as an enum yet, and we're within signed 32-bit range, we assume this is an unknown enum
@@ -78,7 +73,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeInt64 type64)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/MacroValueNode.cs b/Underanalyzer/Decompiler/AST/Nodes/MacroValueNode.cs
index 89f6091..4c55358 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/MacroValueNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/MacroValueNode.cs
@@ -12,12 +12,12 @@ namespace Underanalyzer.Decompiler.AST;
/// Represents a reference to a single macro value in the AST.
/// This is only generated during AST cleanup, so the stack type is undefined.
///
-public class MacroValueNode : IExpressionNode, IConditionalValueNode
+public class MacroValueNode(string valueName) : IExpressionNode, IConditionalValueNode
{
///
/// The content of the macro value name.
///
- public string ValueName { get; }
+ public string ValueName { get; } = valueName;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -26,11 +26,6 @@ public class MacroValueNode : IExpressionNode, IConditionalValueNode
public string ConditionalTypeName => "MacroValue";
public string ConditionalValue => ValueName;
- public MacroValueNode(string valueName)
- {
- ValueName = valueName;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -54,7 +49,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/NewObjectNode.cs b/Underanalyzer/Decompiler/AST/Nodes/NewObjectNode.cs
index 0a97e14..4346cf2 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/NewObjectNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/NewObjectNode.cs
@@ -12,17 +12,18 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents the "new" keyword being used to instantiate an object in the AST.
///
-public class NewObjectNode : IExpressionNode, IStatementNode, IConditionalValueNode, IFunctionCallNode
+public class NewObjectNode(IExpressionNode function, List arguments)
+ : IExpressionNode, IStatementNode, IConditionalValueNode, IFunctionCallNode
{
///
/// The function (constructor) being used.
///
- public IExpressionNode Function { get; private set; }
+ public IExpressionNode Function { get; private set; } = function;
///
/// The arguments passed into the function (constructor).
///
- public List Arguments { get; private set; }
+ public List Arguments { get; private set; } = arguments;
public bool Duplicated { get; set; }
public bool Group { get; set; } = false;
@@ -30,17 +31,11 @@ public class NewObjectNode : IExpressionNode, IStatementNode, IConditionalValueN
public bool SemicolonAfter => true;
public bool EmptyLineBefore => false;
public bool EmptyLineAfter => false;
- public string FunctionName { get => (Function is FunctionReferenceNode functionRef) ? functionRef.Function.Name.Content : null; }
+ public string? FunctionName { get => (Function is FunctionReferenceNode functionRef) ? functionRef.Function.Name.Content : null; }
public string ConditionalTypeName => "NewObject";
public string ConditionalValue => ""; // TODO?
- public NewObjectNode(IExpressionNode function, List arguments)
- {
- Function = function;
- Arguments = arguments;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
Function = Function.Clean(cleaner);
@@ -113,7 +108,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/NullishCoalesceNode.cs b/Underanalyzer/Decompiler/AST/Nodes/NullishCoalesceNode.cs
index 344ba20..6f96597 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/NullishCoalesceNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/NullishCoalesceNode.cs
@@ -11,17 +11,17 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a nullish coalescing operator (??) in the AST.
///
-public class NullishCoalesceNode : IMultiExpressionNode, IConditionalValueNode
+public class NullishCoalesceNode(IExpressionNode left, IExpressionNode right) : IMultiExpressionNode, IConditionalValueNode
{
///
/// The left side of the operator.
///
- public IExpressionNode Left { get; private set; }
+ public IExpressionNode Left { get; private set; } = left;
///
/// The right side of the operator.
///
- public IExpressionNode Right { get; private set; }
+ public IExpressionNode Right { get; private set; } = right;
public bool Duplicated { get; set; }
public bool Group { get; set; } = false;
@@ -30,12 +30,6 @@ public class NullishCoalesceNode : IMultiExpressionNode, IConditionalValueNode
public string ConditionalTypeName => "NullishCoalesce";
public string ConditionalValue => ""; // TODO?
- public NullishCoalesceNode(IExpressionNode left, IExpressionNode right)
- {
- Left = left;
- Right = right;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
Left = Left.Clean(cleaner);
@@ -75,7 +69,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return Left.RequiresMultipleLines(printer) || Right.RequiresMultipleLines(printer);
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/PredefinedDoubleNode.cs b/Underanalyzer/Decompiler/AST/Nodes/PredefinedDoubleNode.cs
index c88e662..442a4cd 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/PredefinedDoubleNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/PredefinedDoubleNode.cs
@@ -11,10 +11,10 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a predefined double constant in the AST, with one single part.
///
-public class PredefinedDoubleSingleNode : IExpressionNode, IConditionalValueNode
+public class PredefinedDoubleSingleNode(string value, double originalValue) : IExpressionNode, IConditionalValueNode
{
- public string Value { get; }
- public double OriginalValue { get; }
+ public string Value { get; } = value;
+ public double OriginalValue { get; } = originalValue;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -23,12 +23,6 @@ public class PredefinedDoubleSingleNode : IExpressionNode, IConditionalValueNode
public string ConditionalTypeName => "PredefinedDouble";
public string ConditionalValue => Value;
- public PredefinedDoubleSingleNode(string value, double originalValue)
- {
- Value = value;
- OriginalValue = originalValue;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -44,7 +38,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
@@ -57,12 +51,9 @@ public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
///
/// Represents a predefined double constant in the AST, with multiple parts.
///
-public class PredefinedDoubleMultiNode : PredefinedDoubleSingleNode, IMultiExpressionNode
+public class PredefinedDoubleMultiNode(string value, double originalValue)
+ : PredefinedDoubleSingleNode(value, originalValue), IMultiExpressionNode
{
- public PredefinedDoubleMultiNode(string value, double originalValue) : base(value, originalValue)
- {
- }
-
public override void Print(ASTPrinter printer)
{
if (Group)
diff --git a/Underanalyzer/Decompiler/AST/Nodes/RepeatLoopNode.cs b/Underanalyzer/Decompiler/AST/Nodes/RepeatLoopNode.cs
index 717f8d3..dbe939e 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/RepeatLoopNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/RepeatLoopNode.cs
@@ -9,28 +9,22 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a repeat loop in the AST.
///
-public class RepeatLoopNode : IStatementNode
+public class RepeatLoopNode(IExpressionNode timesToRepeat, BlockNode body) : IStatementNode
{
///
/// The number of times the loop repeats.
///
- public IExpressionNode TimesToRepeat { get; private set; }
+ public IExpressionNode TimesToRepeat { get; private set; } = timesToRepeat;
///
/// The main block of the loop.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public RepeatLoopNode(IExpressionNode timesToRepeat, BlockNode body)
- {
- TimesToRepeat = timesToRepeat;
- Body = body;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
TimesToRepeat = TimesToRepeat.Clean(cleaner);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ReturnNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ReturnNode.cs
index e290cdd..91dce82 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ReturnNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ReturnNode.cs
@@ -11,29 +11,24 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a "return" statement (with a value) in the AST.
///
-public class ReturnNode : IStatementNode, IBlockCleanupNode
+public class ReturnNode(IExpressionNode value) : IStatementNode, IBlockCleanupNode
{
///
/// Expression being returned.
///
- public IExpressionNode Value { get; private set; }
+ public IExpressionNode Value { get; private set; } = value;
public bool SemicolonAfter => true;
public bool EmptyLineBefore => false;
public bool EmptyLineAfter => false;
- public ReturnNode(IExpressionNode value)
- {
- Value = value;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Value = Value.Clean(cleaner);
// Handle macro type resolution
if (Value is IMacroResolvableNode valueResolvable &&
- cleaner.GlobalMacroResolver.ResolveReturnValueType(cleaner, cleaner.TopFragmentContext.CodeEntryName) is IMacroType returnMacroType &&
+ cleaner.GlobalMacroResolver.ResolveReturnValueType(cleaner, cleaner.TopFragmentContext!.CodeEntryName) is IMacroType returnMacroType &&
valueResolvable.ResolveMacroType(cleaner, returnMacroType) is IExpressionNode valueResolved)
{
Value = valueResolved;
@@ -65,7 +60,7 @@ public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
if (i > 0 && Value is VariableNode returnVariable &&
returnVariable is { Variable.Name.Content: VMConstants.TempReturnVariable })
{
- if (block.Children[i - 1] is AssignNode assign &&
+ if (block.Children[i - 1] is AssignNode { Value: not null, AssignKind: AssignNode.AssignType.Normal } assign &&
assign.Variable is VariableNode { Variable.Name.Content: VMConstants.TempReturnVariable })
{
// We found one - rewrite it as a normal return
@@ -76,7 +71,7 @@ public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
}
// Remove duplicated finally statements (done on second pass)
- if (cleaner.TopFragmentContext.FinallyStatementCount.Count > 0)
+ if (cleaner.TopFragmentContext!.FinallyStatementCount.Count > 0)
{
int count = 0;
foreach (int statementCount in cleaner.TopFragmentContext.FinallyStatementCount)
@@ -89,7 +84,7 @@ public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
// Additionally remove temporary variable, if it exists
if (i - count - 1 >= 0 &&
- block.Children[i - count - 1] is AssignNode assign &&
+ block.Children[i - count - 1] is AssignNode { Value: not null, AssignKind: AssignNode.AssignType.Normal } assign &&
assign.Variable is VariableNode { Variable.Name.Content: VMConstants.TryCopyVariable,
Variable.InstanceType: IGMInstruction.InstanceType.Local })
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ShortCircuitNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ShortCircuitNode.cs
index b020baf..2852ce0 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ShortCircuitNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ShortCircuitNode.cs
@@ -12,28 +12,22 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents short circuit && and || in the AST.
///
-public class ShortCircuitNode : IMultiExpressionNode
+public class ShortCircuitNode(List conditions, ShortCircuitType logicType) : IMultiExpressionNode
{
///
/// List of conditions in this short circuit chain.
///
- public List Conditions { get; private set; }
+ public List Conditions { get; private set; } = conditions;
///
/// Type of logic (And or Or) being used.
///
- public ShortCircuitType LogicType { get; }
+ public ShortCircuitType LogicType { get; } = logicType;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
public IGMInstruction.DataType StackType { get; set; } = IGMInstruction.DataType.Boolean;
- public ShortCircuitNode(List conditions, ShortCircuitType logicType)
- {
- Conditions = conditions;
- LogicType = logicType;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
for (int i = 0; i < Conditions.Count; i++)
diff --git a/Underanalyzer/Decompiler/AST/Nodes/StaticInitNode.cs b/Underanalyzer/Decompiler/AST/Nodes/StaticInitNode.cs
index 1f3a3ad..8567543 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/StaticInitNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/StaticInitNode.cs
@@ -9,22 +9,17 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a static initialization block in the AST.
///
-public class StaticInitNode : IStatementNode
+public class StaticInitNode(BlockNode body) : IStatementNode
{
///
/// The main block of the static initialization.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public StaticInitNode(BlockNode body)
- {
- Body = body;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Body.Clean(cleaner);
@@ -37,7 +32,7 @@ public IStatementNode Clean(ASTCleaner cleaner)
public void Print(ASTPrinter printer)
{
- bool prevStaticInitState = printer.TopFragmentContext.InStaticInitialization;
+ bool prevStaticInitState = printer.TopFragmentContext!.InStaticInitialization;
printer.TopFragmentContext.InStaticInitialization = true;
Body.Print(printer);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/StringNode.cs b/Underanalyzer/Decompiler/AST/Nodes/StringNode.cs
index 607cc38..479e14d 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/StringNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/StringNode.cs
@@ -12,9 +12,9 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a string constant in the AST.
///
-public class StringNode : IConstantNode, IConditionalValueNode
+public class StringNode(IGMString value) : IConstantNode, IConditionalValueNode
{
- public IGMString Value { get; }
+ public IGMString Value { get; } = value;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -23,11 +23,6 @@ public class StringNode : IConstantNode, IConditionalValueNode
public string ConditionalTypeName => "String";
public string ConditionalValue => Value.Content;
- public StringNode(IGMString value)
- {
- Value = value;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
@@ -124,7 +119,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/StructNode.cs b/Underanalyzer/Decompiler/AST/Nodes/StructNode.cs
index 8f83ab2..564e281 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/StructNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/StructNode.cs
@@ -12,12 +12,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// A struct declaration/instantiation within the AST.
///
-public class StructNode : IFragmentNode, IExpressionNode, IConditionalValueNode
+public class StructNode(BlockNode body, ASTFragmentContext fragmentContext) : IFragmentNode, IExpressionNode, IConditionalValueNode
{
///
/// The body of the struct (typically a block with assignments).
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
@@ -25,17 +25,11 @@ public class StructNode : IFragmentNode, IExpressionNode, IConditionalValueNode
public bool SemicolonAfter => false;
public bool EmptyLineBefore => false;
public bool EmptyLineAfter => false;
- public ASTFragmentContext FragmentContext { get; }
+ public ASTFragmentContext FragmentContext { get; } = fragmentContext;
public string ConditionalTypeName => "Struct";
public string ConditionalValue => "";
- public StructNode(BlockNode body, ASTFragmentContext fragmentContext)
- {
- Body = body;
- FragmentContext = fragmentContext;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
Body.Clean(cleaner);
@@ -65,7 +59,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return Body.Children.Count != 0;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/SwitchCaseNode.cs b/Underanalyzer/Decompiler/AST/Nodes/SwitchCaseNode.cs
index b1f2674..21e637b 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/SwitchCaseNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/SwitchCaseNode.cs
@@ -7,24 +7,19 @@ This Source Code Form is subject to the terms of the Mozilla Public
namespace Underanalyzer.Decompiler.AST;
///
-/// Represents a switch case in the AST.
+/// Represents a switch case (or default case) in the AST.
///
-public class SwitchCaseNode : IStatementNode, IBlockCleanupNode
+public class SwitchCaseNode(IExpressionNode? expression) : IStatementNode, IBlockCleanupNode
{
///
- /// The case expression, or null if default.
+ /// The case expression, or if default.
///
- public IExpressionNode Expression { get; internal set; }
+ public IExpressionNode? Expression { get; internal set; } = expression;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public SwitchCaseNode(IExpressionNode expression)
- {
- Expression = expression;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Expression = Expression?.Clean(cleaner);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/SwitchNode.cs b/Underanalyzer/Decompiler/AST/Nodes/SwitchNode.cs
index 16269cc..dfb4aeb 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/SwitchNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/SwitchNode.cs
@@ -11,28 +11,22 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a switch statement in the AST.
///
-public class SwitchNode : IStatementNode
+public class SwitchNode(IExpressionNode expression, BlockNode body) : IStatementNode
{
///
/// The expression being switched upon.
///
- public IExpressionNode Expression { get; private set; }
+ public IExpressionNode Expression { get; private set; } = expression;
///
/// The main block of the switch statement.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public SwitchNode(IExpressionNode expression, BlockNode body)
- {
- Expression = expression;
- Body = body;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Expression = Expression.Clean(cleaner);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/ThrowNode.cs b/Underanalyzer/Decompiler/AST/Nodes/ThrowNode.cs
index dccf4d9..03f4d22 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/ThrowNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/ThrowNode.cs
@@ -9,12 +9,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents the "throw" keyword being used to throw an object/exception in the AST.
///
-public class ThrowNode : IExpressionNode, IStatementNode, IBlockCleanupNode
+public class ThrowNode(IExpressionNode value) : IExpressionNode, IStatementNode, IBlockCleanupNode
{
///
/// The value being thrown.
///
- public IExpressionNode Value { get; private set; }
+ public IExpressionNode Value { get; private set; } = value;
public bool Duplicated { get; set; }
public bool Group { get; set; } = false;
@@ -23,11 +23,6 @@ public class ThrowNode : IExpressionNode, IStatementNode, IBlockCleanupNode
public bool EmptyLineBefore => false;
public bool EmptyLineAfter => false;
- public ThrowNode(IExpressionNode value)
- {
- Value = value;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
Value = Value.Clean(cleaner);
@@ -43,7 +38,7 @@ IStatementNode IASTNode.Clean(ASTCleaner cleaner)
public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
{
// Remove duplicated finally statements
- if (cleaner.TopFragmentContext.FinallyStatementCount.Count > 0 &&
+ if (cleaner.TopFragmentContext!.FinallyStatementCount.Count > 0 &&
cleaner.Context.GameContext.UsingFinallyBeforeThrow)
{
int count = cleaner.TopFragmentContext.FinallyStatementCount.Peek();
diff --git a/Underanalyzer/Decompiler/AST/Nodes/TryCatchNode.cs b/Underanalyzer/Decompiler/AST/Nodes/TryCatchNode.cs
index 822cbbd..ab75639 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/TryCatchNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/TryCatchNode.cs
@@ -11,49 +11,42 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a try/catch, try/catch/finally, or try/finally statement in the AST.
///
-public class TryCatchNode : IStatementNode
+public class TryCatchNode(BlockNode tryBlock, BlockNode? catchBlock, VariableNode? catchVariable) : IStatementNode
{
///
/// The block inside of "try".
///
- public BlockNode Try { get; }
-
+ public BlockNode Try { get; } = tryBlock;
+
///
- /// The block inside of "catch", or null if none exists.
+ /// The block inside of "catch", or if none exists.
///
- public BlockNode Catch { get; }
+ public BlockNode? Catch { get; } = catchBlock;
///
- /// The variable used to store the thrown value for the catch block, if Catch is not null.
+ /// The variable used to store the thrown value for the catch block, if Catch is not .
///
- public VariableNode CatchVariable { get; }
+ public VariableNode? CatchVariable { get; } = catchVariable;
///
- /// The block inside of "finally", or null if none exists.
+ /// The block inside of "finally", or if none exists.
///
- public BlockNode Finally { get; internal set; } = null;
+ public BlockNode? Finally { get; internal set; } = null;
///
- /// Compiler-generated variable name used for break, or null if none.
+ /// Compiler-generated variable name used for break, or if none.
///
- public string BreakVariableName { get; internal set; } = null;
+ public string? BreakVariableName { get; internal set; } = null;
///
- /// Compiler-generated variable name used for continue, or null if none.
+ /// Compiler-generated variable name used for continue, or if none.
///
- public string ContinueVariableName { get; internal set; } = null;
+ public string? ContinueVariableName { get; internal set; } = null;
public bool SemicolonAfter => false;
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public TryCatchNode(BlockNode tryBlock, BlockNode catchBlock, VariableNode catchVariable)
- {
- Try = tryBlock;
- Catch = catchBlock;
- CatchVariable = catchVariable;
- }
-
// Cleans out compiler-generated control flow from individual try or catch blocks.
private void CleanPart(BlockNode node)
{
@@ -66,7 +59,7 @@ private void CleanPart(BlockNode node)
{
return;
}
- if (ifNode is not { Condition: VariableNode continueVar, TrueBlock: { Children: [BreakNode] }, ElseBlock: null })
+ if (ifNode is not { Condition: VariableNode continueVar, TrueBlock.Children: [BreakNode], ElseBlock: null })
{
return;
}
@@ -91,7 +84,7 @@ public IStatementNode Clean(ASTCleaner cleaner)
if (cleaner.Context.Settings.CleanupTry)
{
// Push finally context
- cleaner.TopFragmentContext.FinallyStatementCount.Push(Finally.Children.Count);
+ cleaner.TopFragmentContext!.FinallyStatementCount.Push(Finally.Children.Count);
}
}
@@ -109,7 +102,7 @@ public IStatementNode Clean(ASTCleaner cleaner)
if (Finally is not null)
{
// Pop finally context
- cleaner.TopFragmentContext.FinallyStatementCount.Pop();
+ cleaner.TopFragmentContext!.FinallyStatementCount.Pop();
}
// Cleanup continue/break
@@ -123,7 +116,7 @@ public IStatementNode Clean(ASTCleaner cleaner)
}
// Remove local variable names
- cleaner.TopFragmentContext.RemoveLocal(BreakVariableName);
+ cleaner.TopFragmentContext!.RemoveLocal(BreakVariableName);
cleaner.TopFragmentContext.RemoveLocal(ContinueVariableName);
}
}
@@ -153,7 +146,7 @@ public void Print(ASTPrinter printer)
printer.StartLine();
}
printer.Write("catch (");
- CatchVariable.Print(printer);
+ CatchVariable!.Print(printer);
printer.Write(')');
if (printer.Context.Settings.OpenBlockBraceOnSameLine)
{
@@ -218,9 +211,11 @@ public int BlockClean(ASTCleaner cleaner, BlockNode block, int i)
if (curr is TryCatchNode tryCatchNode)
{
// Create finally block with all statements in between
- BlockNode finallyBlock = new(block.FragmentContext);
- finallyBlock.UseBraces = true;
- finallyBlock.Children = block.Children.GetRange(j + 1, i - (j + 1));
+ BlockNode finallyBlock = new(block.FragmentContext)
+ {
+ UseBraces = true,
+ Children = block.Children.GetRange(j + 1, i - (j + 1))
+ };
block.Children.RemoveRange(j + 1, i - (j + 1));
// Assign finally block, and re-clean try statement
diff --git a/Underanalyzer/Decompiler/AST/Nodes/UnaryNode.cs b/Underanalyzer/Decompiler/AST/Nodes/UnaryNode.cs
index a16ce53..48cdea0 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/UnaryNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/UnaryNode.cs
@@ -12,32 +12,25 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a unary expression, such as not (!) and bitwise negation (~).
///
-public class UnaryNode : IExpressionNode, IConditionalValueNode
+public class UnaryNode(IExpressionNode value, IGMInstruction instruction) : IExpressionNode, IConditionalValueNode
{
///
/// The expression that this operation is being performed on.
///
- public IExpressionNode Value { get; private set; }
+ public IExpressionNode Value { get; private set; } = value;
///
/// The instruction that performs this operation, as in the code.
///
- public IGMInstruction Instruction { get; }
+ public IGMInstruction Instruction { get; } = instruction;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
- public IGMInstruction.DataType StackType { get; set; }
+ public DataType StackType { get; set; } = instruction.Type1;
public string ConditionalTypeName => "Unary";
public string ConditionalValue => ""; // TODO?
- public UnaryNode(IExpressionNode value, IGMInstruction instruction)
- {
- Value = value;
- Instruction = instruction;
- StackType = instruction.Type1;
- }
-
public IExpressionNode Clean(ASTCleaner cleaner)
{
Value = Value.Clean(cleaner);
@@ -80,7 +73,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return Value.RequiresMultipleLines(printer);
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/VariableCallNode.cs b/Underanalyzer/Decompiler/AST/Nodes/VariableCallNode.cs
index f43dab6..bacfd59 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/VariableCallNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/VariableCallNode.cs
@@ -12,22 +12,23 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a variable being called as a method/function in the AST.
///
-public class VariableCallNode : IExpressionNode, IStatementNode, IConditionalValueNode, IFunctionCallNode
+public class VariableCallNode(IExpressionNode function, IExpressionNode? instance, List arguments)
+ : IExpressionNode, IStatementNode, IConditionalValueNode, IFunctionCallNode
{
///
/// The function/method variable being called.
///
- public IExpressionNode Function { get; private set; }
+ public IExpressionNode Function { get; private set; } = function;
///
- /// The instance the method is being called on.
+ /// The instance the method is being called on, or if none.
///
- public IExpressionNode Instance { get; private set; }
+ public IExpressionNode? Instance { get; private set; } = instance;
///
/// The arguments used in the call.
///
- public List Arguments { get; }
+ public List Arguments { get; } = arguments;
public bool Duplicated { get; set; }
public bool Group { get; set; } = false;
@@ -35,18 +36,11 @@ public class VariableCallNode : IExpressionNode, IStatementNode, IConditionalVal
public bool SemicolonAfter => true;
public bool EmptyLineBefore => false;
public bool EmptyLineAfter => false;
- public string FunctionName => null;
+ public string? FunctionName => null;
public string ConditionalTypeName => "VariableCall";
public string ConditionalValue => ""; // TODO?
- public VariableCallNode(IExpressionNode function, IExpressionNode instance, List arguments)
- {
- Function = function;
- Instance = instance;
- Arguments = arguments;
- }
-
IExpressionNode IASTNode.Clean(ASTCleaner cleaner)
{
Function = Function.Clean(cleaner);
@@ -83,7 +77,7 @@ public void Print(ASTPrinter printer)
// Have to also check if we *need* "self." or not, if that's what Instance happens to be.
if (Instance is not InstanceTypeNode instType2 || instType2.InstanceType != IGMInstruction.InstanceType.Self || // TODO: for later investigation: does Builtin also need to be checked in 2024 versions?
printer.LocalVariableNames.Contains(variable.Variable.Name.Content) ||
- printer.TopFragmentContext.NamedArguments.Contains(variable.Variable.Name.Content))
+ printer.TopFragmentContext!.NamedArguments.Contains(variable.Variable.Name.Content))
{
Instance.Print(printer);
printer.Write('.');
@@ -137,7 +131,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/VariableHashNode.cs b/Underanalyzer/Decompiler/AST/Nodes/VariableHashNode.cs
index d119a0e..8844a57 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/VariableHashNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/VariableHashNode.cs
@@ -11,12 +11,12 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a variable hash in the AST, generated at compile-time in more recent GMLv2 versions.
///
-public class VariableHashNode : IExpressionNode, IStatementNode, IConditionalValueNode
+public class VariableHashNode(IGMVariable variable) : IExpressionNode, IStatementNode, IConditionalValueNode
{
///
/// The variable being referenced.
///
- public IGMVariable Variable;
+ public IGMVariable Variable = variable;
public bool Duplicated { get; set; }
public bool Group { get; set; } = false;
@@ -28,11 +28,6 @@ public class VariableHashNode : IExpressionNode, IStatementNode, IConditionalVal
public string ConditionalTypeName => "VariableHash";
public string ConditionalValue => Variable.Name.Content; // TODO?
- public VariableHashNode(IGMVariable variable)
- {
- Variable = variable;
- }
-
IExpressionNode IASTNode.Clean(ASTCleaner cleaner)
{
return this;
@@ -65,7 +60,7 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/VariableNode.cs b/Underanalyzer/Decompiler/AST/Nodes/VariableNode.cs
index 0b52310..7db632c 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/VariableNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/VariableNode.cs
@@ -13,47 +13,42 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a variable reference in the AST.
///
-public class VariableNode : IExpressionNode, IMacroTypeNode, IConditionalValueNode
+public class VariableNode(IGMVariable variable, VariableType referenceType, IExpressionNode left,
+ List? arrayIndices = null, bool regularPush = false)
+ : IExpressionNode, IMacroTypeNode, IConditionalValueNode
{
///
/// The variable being referenced.
///
- public IGMVariable Variable { get; }
+ public IGMVariable Variable { get; } = variable;
///
/// The type of the variable reference.
///
- public IGMInstruction.VariableType ReferenceType { get; }
+ public VariableType ReferenceType { get; } = referenceType;
///
/// The left side of the variable (before a dot, usually).
///
- public IExpressionNode Left { get; internal set; }
+ public IExpressionNode Left { get; internal set; } = left;
///
- /// For array accesses, this is not null, and contains all array indexing operations on this variable.
+ /// For array accesses, this is not , and contains all array indexing operations on this variable.
///
- public List ArrayIndices { get; internal set; }
+ public List? ArrayIndices { get; internal set; } = arrayIndices;
///
- /// If true, means that this variable was pushed with a normal opcode.
+ /// If true, means that this variable was pushed with a normal opcode.
///
- public bool RegularPush { get; }
+ public bool RegularPush { get; } = regularPush;
public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
- public IGMInstruction.DataType StackType { get; set; } = IGMInstruction.DataType.Variable;
+ public DataType StackType { get; set; } = DataType.Variable;
public string ConditionalTypeName => "Variable";
public string ConditionalValue => Variable.Name.Content;
- public VariableNode(IGMVariable variable, IGMInstruction.VariableType referenceType, bool regularPush = false)
- {
- Variable = variable;
- ReferenceType = referenceType;
- RegularPush = regularPush;
- }
-
///
/// Returns true if the other variable is referencing an identical variable, within the same expression/statement.
///
@@ -68,21 +63,33 @@ public bool IdenticalToInExpression(VariableNode other)
// Compare left side
if (Left is VariableNode leftVariable)
{
- if (!leftVariable.IdenticalToInExpression(other.Left as VariableNode))
+ if (other.Left is not VariableNode otherLeftVariable)
+ {
+ return false;
+ }
+ if (!leftVariable.IdenticalToInExpression(otherLeftVariable))
{
return false;
}
}
else if (Left is InstanceTypeNode leftInstType)
{
- if (leftInstType.InstanceType != (other.Left as InstanceTypeNode).InstanceType)
+ if (other.Left is not InstanceTypeNode otherLeftInstType)
+ {
+ return false;
+ }
+ if (leftInstType.InstanceType != otherLeftInstType.InstanceType)
{
return false;
}
}
else if (Left is Int16Node leftI16)
{
- if (leftI16.Value != (other.Left as Int16Node).Value)
+ if (other.Left is not Int16Node otherLeftI16)
+ {
+ return false;
+ }
+ if (leftI16.Value != otherLeftI16.Value)
{
return false;
}
@@ -139,21 +146,33 @@ public bool SimilarToInForIncrementor(VariableNode other)
// Compare left side
if (Left is VariableNode leftVariable)
{
- if (!leftVariable.IdenticalToInExpression(other.Left as VariableNode))
+ if (other.Left is not VariableNode otherLeftVariable)
+ {
+ return false;
+ }
+ if (!leftVariable.IdenticalToInExpression(otherLeftVariable))
{
return false;
}
}
else if (Left is InstanceTypeNode leftInstType)
{
- if (leftInstType.InstanceType != (other.Left as InstanceTypeNode).InstanceType)
+ if (other.Left is not InstanceTypeNode otherLeftInstType)
+ {
+ return false;
+ }
+ if (leftInstType.InstanceType != otherLeftInstType.InstanceType)
{
return false;
}
}
else if (Left is Int16Node leftI16)
{
- if (leftI16.Value != (other.Left as Int16Node).Value)
+ if (other.Left is not Int16Node otherLeftI16)
+ {
+ return false;
+ }
+ if (leftI16.Value != otherLeftI16.Value)
{
return false;
}
@@ -220,7 +239,7 @@ public IExpressionNode Clean(ASTCleaner cleaner)
int num = GetArgumentIndex();
if (num != -1)
{
- if (num > cleaner.TopFragmentContext.MaxReferencedArgument)
+ if (num > cleaner.TopFragmentContext!.MaxReferencedArgument)
{
// We have a new maximum!
cleaner.TopFragmentContext.MaxReferencedArgument = num;
@@ -237,12 +256,12 @@ public IExpressionNode Clean(ASTCleaner cleaner)
public void Print(ASTPrinter printer)
{
// Print out left side, if necessary
- Int16Node leftI16 = Left as Int16Node;
- InstanceTypeNode leftInstType = Left as InstanceTypeNode;
+ Int16Node? leftI16 = Left as Int16Node;
+ InstanceTypeNode? leftInstType = Left as InstanceTypeNode;
if (leftI16 is not null || leftInstType is not null)
{
// Basic numerical instance type
- int value = leftI16?.Value ?? (int)leftInstType.InstanceType;
+ int value = leftI16?.Value ?? (int)leftInstType!.InstanceType;
if (value < 0)
{
// GameMaker constant instance types
@@ -251,7 +270,7 @@ public void Print(ASTPrinter printer)
case (int)InstanceType.Self:
case (int)InstanceType.Builtin:
if (printer.LocalVariableNames.Contains(Variable.Name.Content) ||
- printer.TopFragmentContext.NamedArguments.Contains(Variable.Name.Content))
+ printer.TopFragmentContext!.NamedArguments.Contains(Variable.Name.Content))
{
// Need an explicit self in order to not conflict with local
printer.Write("self.");
@@ -279,8 +298,7 @@ public void Print(ASTPrinter printer)
else
{
// Check if we have an object asset name to use
- string objectName = printer.Context.GameContext.GetAssetName(AssetType.Object, value);
-
+ string? objectName = printer.Context.GameContext.GetAssetName(AssetType.Object, value);
if (objectName is not null)
{
// Object asset
@@ -312,7 +330,7 @@ public void Print(ASTPrinter printer)
else
{
// Argument name
- string namedArg = printer.TopFragmentContext.GetNamedArgumentName(printer.Context, argIndex);
+ string? namedArg = printer.TopFragmentContext!.GetNamedArgumentName(printer.Context, argIndex);
if (namedArg is not null)
{
printer.Write(namedArg);
@@ -370,12 +388,12 @@ public bool RequiresMultipleLines(ASTPrinter printer)
return false;
}
- public IMacroType GetExpressionMacroType(ASTCleaner cleaner)
+ public IMacroType? GetExpressionMacroType(ASTCleaner cleaner)
{
return cleaner.GlobalMacroResolver.ResolveVariableType(cleaner, Variable.Name.Content);
}
- public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
+ public IExpressionNode? ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
diff --git a/Underanalyzer/Decompiler/AST/Nodes/WhileLoopNode.cs b/Underanalyzer/Decompiler/AST/Nodes/WhileLoopNode.cs
index 7984ba9..bd4b0ce 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/WhileLoopNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/WhileLoopNode.cs
@@ -9,35 +9,28 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a while loop in the AST.
///
-public class WhileLoopNode : IStatementNode, IBlockCleanupNode
+public class WhileLoopNode(IExpressionNode condition, BlockNode body, bool mustBeWhileLoop) : IStatementNode, IBlockCleanupNode
{
///
/// The condition of the loop.
///
- public IExpressionNode Condition { get; private set; }
+ public IExpressionNode Condition { get; private set; } = condition;
///
/// The main block of the loop.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
///
/// True if this loop was specifically detected to be a while loop already.
/// That is, if true, this cannot be rewritten as a for loop.
///
- public bool MustBeWhileLoop { get; }
+ public bool MustBeWhileLoop { get; } = mustBeWhileLoop;
public bool SemicolonAfter { get => false; }
public bool EmptyLineBefore { get; internal set; }
public bool EmptyLineAfter { get; internal set; }
- public WhileLoopNode(IExpressionNode condition, BlockNode body, bool mustBeWhileLoop)
- {
- Condition = condition;
- Body = body;
- MustBeWhileLoop = mustBeWhileLoop;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Condition = Condition.Clean(cleaner);
diff --git a/Underanalyzer/Decompiler/AST/Nodes/WithLoopNode.cs b/Underanalyzer/Decompiler/AST/Nodes/WithLoopNode.cs
index 6c06907..7c1b645 100644
--- a/Underanalyzer/Decompiler/AST/Nodes/WithLoopNode.cs
+++ b/Underanalyzer/Decompiler/AST/Nodes/WithLoopNode.cs
@@ -9,28 +9,22 @@ namespace Underanalyzer.Decompiler.AST;
///
/// Represents a with loop in the AST.
///
-public class WithLoopNode : IStatementNode
+public class WithLoopNode(IExpressionNode target, BlockNode body) : IStatementNode
{
///
/// The target of the with loop (object/instance).
///
- public IExpressionNode Target { get; private set; }
+ public IExpressionNode Target { get; private set; } = target;
///
/// The main block of the loop.
///
- public BlockNode Body { get; private set; }
+ public BlockNode Body { get; private set; } = body;
public bool SemicolonAfter { get => false; }
public bool EmptyLineBefore { get; private set; }
public bool EmptyLineAfter { get; private set; }
- public WithLoopNode(IExpressionNode target, BlockNode body)
- {
- Target = target;
- Body = body;
- }
-
public IStatementNode Clean(ASTCleaner cleaner)
{
Target = Target.Clean(cleaner);
diff --git a/Underanalyzer/Decompiler/ControlFlow/BinaryBranch.cs b/Underanalyzer/Decompiler/ControlFlow/BinaryBranch.cs
index 5c0b0bb..619b214 100644
--- a/Underanalyzer/Decompiler/ControlFlow/BinaryBranch.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/BinaryBranch.cs
@@ -18,47 +18,50 @@ internal class BinaryBranch : IControlFlowNode
public int EndAddress { get; private set; }
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = [null, null, null, null];
+ public List Children { get; } = [null, null, null, null];
public bool Unreachable { get; set; } = false;
///
/// The "condition" block of the if statement.
///
- public IControlFlowNode Condition { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Condition { get => Children[0]!; private set => Children[0] = value; }
///
/// The "true" block of the if statement.
///
- public IControlFlowNode True { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode True { get => Children[1]!; private set => Children[1] = value; }
///
/// The "false" block of the if statement.
///
- public IControlFlowNode False { get => Children[2]; private set => Children[2] = value; }
+ public IControlFlowNode False { get => Children[2]!; private set => Children[2] = value; }
///
- /// The "else" block of the if statement, or null if none exists.
+ /// The "else" block of the if statement, or if none exists.
///
- public IControlFlowNode Else { get => Children[3]; private set => Children[3] = value; }
+ public IControlFlowNode? Else { get => Children[3]; private set => Children[3] = value; }
- public BinaryBranch(int startAddress, int endAddress)
+ public BinaryBranch(int startAddress, int endAddress, IControlFlowNode condition, IControlFlowNode initialTrue, IControlFlowNode initialFalse)
{
StartAddress = startAddress;
EndAddress = endAddress;
+ Condition = condition;
+ True = initialTrue;
+ False = initialFalse;
}
///
/// Visits all nodes that are candidates for the meeting point of the if statement branch, along one path.
/// Marks off in "visited" all such nodes.
///
- private static void VisitAll(IControlFlowNode start, HashSet visited, List blocks)
+ private static void VisitAll(IControlFlowNode start, HashSet visited)
{
Stack work = new();
work.Push(start);
@@ -87,7 +90,7 @@ private static void VisitAll(IControlFlowNode start, HashSet v
/// Visits all nodes that are candidates for the meeting point of the if statement branch, along a second path.
/// Upon finding a node that was visited along the first path (through VisitAll), returns that node.
///
- private static IControlFlowNode FindMeetpoint(IControlFlowNode start, IControlFlowNode mustBeAfter, HashSet visited, List blocks)
+ private static IControlFlowNode FindMeetpoint(IControlFlowNode start, IControlFlowNode mustBeAfter, HashSet visited)
{
Stack work = new();
work.Push(start);
@@ -162,33 +165,30 @@ private static void CleanupAfterPredecessors(BinaryBranch bb, IControlFlowNode a
public static List FindBinaryBranches(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
- List loops = ctx.LoopNodes;
- ctx.BlockSurroundingLoops ??= Branches.FindSurroundingLoops(blocks, ctx.BlocksByAddress, loops);
+ List blocks = ctx.Blocks!;
+ List loops = ctx.LoopNodes!;
+ ctx.BlockSurroundingLoops ??= Branches.FindSurroundingLoops(blocks, ctx.BlocksByAddress!, loops);
ctx.BlockAfterLimits ??= Branches.ComputeBlockAfterLimits(blocks, ctx.BlockSurroundingLoops);
// Resolve all relevant continue/break statements
Branches.ResolveExternalJumps(ctx);
// Iterate over blocks in reverse, as the compiler generates them in the order we want
- List res = new();
- HashSet visited = new();
+ List res = [];
+ HashSet visited = [];
for (int i = blocks.Count - 1; i >= 0; i--)
{
Block block = blocks[i];
if (block.Instructions is [.., { Kind: IGMInstruction.Opcode.BranchFalse }])
{
// Follow "jump" path first, marking off all visited blocks
- VisitAll(block.Successors[1], visited, blocks);
+ VisitAll(block.Successors[1], visited);
// Locate meetpoint, by following the non-jump path
- IControlFlowNode after = FindMeetpoint(block.Successors[0], block.Successors[1], visited, blocks);
+ IControlFlowNode after = FindMeetpoint(block.Successors[0], block.Successors[1], visited);
// Insert new node!
- BinaryBranch bb = new(block.StartAddress, after.StartAddress);
- bb.Condition = block;
- bb.True = block.Successors[0];
- bb.False = block.Successors[1];
+ BinaryBranch bb = new(block.StartAddress, after.StartAddress, block, block.Successors[0], block.Successors[1]);
res.Add(bb);
// Assign else block if we can immediately detect it
@@ -243,7 +243,7 @@ public static List FindBinaryBranches(DecompileContext ctx)
}
if (block.Parent is not null)
{
- IControlFlowNode.ReplaceConnections(block.Parent.Children, block, bb);
+ IControlFlowNode.ReplaceConnectionsNullable(block.Parent.Children, block, bb);
bb.Parent = block.Parent;
}
block.Predecessors.Clear();
diff --git a/Underanalyzer/Decompiler/ControlFlow/Block.cs b/Underanalyzer/Decompiler/ControlFlow/Block.cs
index 09c48ab..1e5c260 100644
--- a/Underanalyzer/Decompiler/ControlFlow/Block.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/Block.cs
@@ -12,33 +12,25 @@ namespace Underanalyzer.Decompiler.ControlFlow;
///
/// Represents a basic block of VM instructions.
///
-internal class Block : IControlFlowNode
+internal class Block(int startAddr, int endAddr, int blockIndex, List instructions) : IControlFlowNode
{
- public int StartAddress { get; private set; }
+ public int StartAddress { get; private set; } = startAddr;
- public int EndAddress { get; private set; }
+ public int EndAddress { get; private set; } = endAddr;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public List Instructions { get; }
+ public List Instructions { get; } = instructions;
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children => null;
+ public List Children => throw new System.NotImplementedException();
public bool Unreachable { get; set; } = false;
- public int BlockIndex { get; }
-
- public Block(int startAddr, int endAddr, int blockIndex, List instructions)
- {
- StartAddress = startAddr;
- EndAddress = endAddr;
- BlockIndex = blockIndex;
- Instructions = instructions;
- }
+ public int BlockIndex { get; } = blockIndex;
///
/// Calculates addresses of basic VM blocks; located at instructions that are jumped to.
@@ -72,11 +64,13 @@ private static HashSet FindBlockAddresses(IGMCode code)
break;
case IGMInstruction.Opcode.Call:
// Handle try hook addresses
- if (i >= 4 && instr.Function.Name?.Content == VMConstants.TryHookFunction)
+ if (i >= 4 && instr.Function?.Name?.Content == VMConstants.TryHookFunction)
{
// If too close to end, bail
if (i >= code.InstructionCount - 1)
+ {
break;
+ }
// Check instructions
IGMInstruction finallyInstr = code.GetInstruction(i - 4);
@@ -95,7 +89,9 @@ private static HashSet FindBlockAddresses(IGMCode code)
int catchBlock = catchInstr.ValueInt;
if (catchBlock != -1)
+ {
addresses.Add(catchBlock);
+ }
// Split this try hook into its own block - removes edge cases in later graph operations
addresses.Add(finallyInstr.Address);
@@ -127,9 +123,9 @@ public static List FindBlocks(IGMCode code, out Dictionary bl
{
HashSet addresses = FindBlockAddresses(code);
- blocksByAddress = new();
- List blocks = new();
- Block current = null;
+ blocksByAddress = [];
+ List blocks = [];
+ Block? current = null;
for (int i = 0; i < code.InstructionCount; i++)
{
// Check if we have a new block at the current instruction's address
@@ -137,7 +133,7 @@ public static List FindBlocks(IGMCode code, out Dictionary bl
if (addresses.Contains(instr.Address))
{
// End previous block
- if (current != null)
+ if (current is not null)
{
current.EndAddress = instr.Address;
}
@@ -149,7 +145,7 @@ public static List FindBlocks(IGMCode code, out Dictionary bl
}
// Add current instruction to our currently-building block
- current.Instructions.Add(instr);
+ current!.Instructions.Add(instr);
}
// End current block, if applicable
@@ -230,7 +226,7 @@ public static List FindBlocks(IGMCode code, out Dictionary bl
{
IGMInstruction callInstr = b.Instructions[^2];
if (callInstr.Kind == IGMInstruction.Opcode.Call &&
- callInstr.Function.Name?.Content == VMConstants.TryHookFunction)
+ callInstr.Function?.Name?.Content == VMConstants.TryHookFunction)
{
// We've found a try hook - connect to targets
int finallyAddr = b.Instructions[^6].ValueInt;
diff --git a/Underanalyzer/Decompiler/ControlFlow/Branches.cs b/Underanalyzer/Decompiler/ControlFlow/Branches.cs
index f257a06..a8127e4 100644
--- a/Underanalyzer/Decompiler/ControlFlow/Branches.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/Branches.cs
@@ -18,7 +18,7 @@ public static Dictionary FindSurroundingLoops(
{
// Assign blocks to loops.
// We assume that loops are sorted so that nested loops come after outer loops.
- Dictionary surroundingLoops = new();
+ Dictionary surroundingLoops = [];
foreach (Loop l in loops)
{
Block startBlock = blockByAddress[l.StartAddress];
@@ -44,7 +44,7 @@ private readonly struct LimitEntry(int limit, bool fromBinary)
///
public static Dictionary ComputeBlockAfterLimits(List blocks, Dictionary surroundingLoops)
{
- Dictionary blockToAfterLimit = new();
+ Dictionary blockToAfterLimit = [];
List limitStack = [new(blocks[^1].EndAddress, false)];
@@ -92,7 +92,7 @@ public static Dictionary ComputeBlockAfterLimits(List blocks,
}
// If we have a loop surrounding this block, we can also use that
- if (surroundingLoops.TryGetValue(b, out Loop loop))
+ if (surroundingLoops.TryGetValue(b, out Loop? loop))
{
if (loop.EndAddress < thisLimit)
{
@@ -198,22 +198,22 @@ private static void InsertExternalJumpNode(IControlFlowNode node, Block block, L
///
public static void ResolveExternalJumps(DecompileContext ctx)
{
- Dictionary surroundingLoops = ctx.BlockSurroundingLoops;
- Dictionary blockAfterLimits = ctx.BlockAfterLimits;
- foreach (Block block in ctx.Blocks)
+ Dictionary surroundingLoops = ctx.BlockSurroundingLoops!;
+ Dictionary blockAfterLimits = ctx.BlockAfterLimits!;
+ foreach (Block block in ctx.Blocks!)
{
if (block.Instructions is [.., { Kind: IGMInstruction.Opcode.Branch }] && block.Successors.Count >= 1)
{
- IControlFlowNode node = null;
+ IControlFlowNode? node = null;
// Check that we're not supposed to be ignored
- if (ctx.SwitchIgnoreJumpBlocks.Contains(block))
+ if (ctx.SwitchIgnoreJumpBlocks!.Contains(block))
{
continue;
}
// Look for a trivial branch to top or end of surrounding loop
- if (surroundingLoops.TryGetValue(block, out Loop loop))
+ if (surroundingLoops.TryGetValue(block, out Loop? loop))
{
if (block.Successors[0] == loop)
{
@@ -256,13 +256,13 @@ public static void ResolveExternalJumps(DecompileContext ctx)
{
// Check if we're breaking/continuing from inside of a switch statement.
IControlFlowNode succNode = block.Successors[0];
- Block succBlock = succNode as Block;
- if (ctx.SwitchEndNodes.Contains(succNode))
+ Block? succBlock = succNode as Block;
+ if (ctx.SwitchEndNodes!.Contains(succNode))
{
// This is a break from inside of a switch
node = new BreakNode(block.EndAddress - 4);
}
- else if (succBlock is not null && ctx.SwitchContinueBlocks.Contains(succBlock))
+ else if (succBlock is not null && ctx.SwitchContinueBlocks!.Contains(succBlock))
{
// This is a continue from inside of a switch
node = new ContinueNode(block.EndAddress - 4);
@@ -361,11 +361,11 @@ private static void InsertRemainingExternalJumpNode(IControlFlowNode node, Block
///
public static void ResolveRemainingExternalJumps(DecompileContext ctx)
{
- foreach (Block block in ctx.Blocks)
+ foreach (Block block in ctx.Blocks!)
{
if (block.Instructions is [.., { Kind: IGMInstruction.Opcode.Branch, Address: int address, BranchOffset: int branchOffset }])
{
- if (!ctx.BlockSurroundingLoops.TryGetValue(block, out Loop loop))
+ if (!ctx.BlockSurroundingLoops!.TryGetValue(block, out Loop? loop))
{
throw new DecompilerException("Expected loop to be around unresolved branch");
}
@@ -377,7 +377,7 @@ public static void ResolveRemainingExternalJumps(DecompileContext ctx)
{
// This is probably a for loop now.
// We need to find the original successor, and set that as the incrementor.
- IControlFlowNode succ = ctx.BlocksByAddress[address + branchOffset];
+ IControlFlowNode succ = ctx.BlocksByAddress![address + branchOffset];
while (succ.Parent is not null)
{
succ = succ.Parent;
diff --git a/Underanalyzer/Decompiler/ControlFlow/BreakNode.cs b/Underanalyzer/Decompiler/ControlFlow/BreakNode.cs
index e8636b2..ed89f1c 100644
--- a/Underanalyzer/Decompiler/ControlFlow/BreakNode.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/BreakNode.cs
@@ -18,13 +18,13 @@ internal class BreakNode(int address, bool mayBeContinue = false) : IControlFlow
public int EndAddress { get; set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
diff --git a/Underanalyzer/Decompiler/ControlFlow/ContinueNode.cs b/Underanalyzer/Decompiler/ControlFlow/ContinueNode.cs
index ba41d4d..ee0cfe2 100644
--- a/Underanalyzer/Decompiler/ControlFlow/ContinueNode.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/ContinueNode.cs
@@ -18,13 +18,13 @@ internal class ContinueNode(int address) : IControlFlowNode
public int EndAddress { get; set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
diff --git a/Underanalyzer/Decompiler/ControlFlow/ControlFlowNode.cs b/Underanalyzer/Decompiler/ControlFlow/ControlFlowNode.cs
index 852b017..029c60c 100644
--- a/Underanalyzer/Decompiler/ControlFlow/ControlFlowNode.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/ControlFlowNode.cs
@@ -34,13 +34,13 @@ internal interface IControlFlowNode
/// If disconnected from the rest of the graph, e.g. at the start of a high-level
/// control flow structure like a loop, this points to the enveloping structure.
///
- public IControlFlowNode Parent { get; set; }
+ public IControlFlowNode? Parent { get; set; }
///
/// If this is a high-level control flow structure like a loop, this represents
/// all relevant internal nodes that this structure requires handles for.
///
- public List Children { get; }
+ public List Children { get; }
///
/// If true, this node's predecessors do not truly exist. That is,
@@ -163,6 +163,20 @@ public static void ReplaceConnections(List list, IControlFlowN
}
}
+ ///
+ /// Helper function to replace all instances of "search" with "replace" in a control flow list.
+ ///
+ public static void ReplaceConnectionsNullable(List list, IControlFlowNode search, IControlFlowNode replace)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ if (list[i] == search)
+ {
+ list[i] = replace;
+ }
+ }
+ }
+
///
/// Utility function to reroute an entire section of control flow (from its beginning and end),
/// to make way for a new high-level control flow structure, such as a loop.
@@ -189,7 +203,7 @@ public static void InsertStructure(IControlFlowNode start, IControlFlowNode afte
}
if (start.Parent is not null)
{
- ReplaceConnections(start.Parent.Children, start, newStructure);
+ ReplaceConnectionsNullable(start.Parent.Children, start, newStructure);
}
// TODO: do we care about "start"'s Children?
start.Predecessors.Clear();
diff --git a/Underanalyzer/Decompiler/ControlFlow/DoUntilLoop.cs b/Underanalyzer/Decompiler/ControlFlow/DoUntilLoop.cs
index 1b5e71c..5b65ebc 100644
--- a/Underanalyzer/Decompiler/ControlFlow/DoUntilLoop.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/DoUntilLoop.cs
@@ -14,7 +14,7 @@ namespace Underanalyzer.Decompiler.ControlFlow;
///
internal class DoUntilLoop : Loop
{
- public override List Children { get; } = [null, null, null];
+ public override List Children { get; } = [null, null, null];
///
/// The top loop point and start of the loop body, as written in the source code.
@@ -22,7 +22,7 @@ internal class DoUntilLoop : Loop
///
/// Upon being processed, this is disconnected from its predecessors.
///
- public IControlFlowNode Head { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Head { get => Children[0]!; private set => Children[0] = value; }
///
/// The bottom loop point of the loop. This is where the loop condition and branch to the loop head is located.
@@ -30,7 +30,7 @@ internal class DoUntilLoop : Loop
///
/// Upon being processed, this is disconnected from its successors.
///
- public IControlFlowNode Tail { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode Tail { get => Children[1]!; private set => Children[1] = value; }
///
/// The "sink" location of the loop. The loop condition being false or "break" statements will lead to this location.
@@ -38,7 +38,7 @@ internal class DoUntilLoop : Loop
///
/// Upon being processed, this becomes a new , which is then disconnected from the external graph.
///
- public IControlFlowNode After { get => Children[2]; private set => Children[2] = value; }
+ public IControlFlowNode After { get => Children[2]!; private set => Children[2] = value; }
public DoUntilLoop(int startAddress, int endAddress, IControlFlowNode head, IControlFlowNode tail, IControlFlowNode after)
: base(startAddress, endAddress)
@@ -53,7 +53,7 @@ public override void UpdateFlowGraph()
// Get rid of jumps from tail
IControlFlowNode.DisconnectSuccessor(Tail, 1);
IControlFlowNode.DisconnectSuccessor(Tail, 0);
- Block tailBlock = Tail as Block;
+ Block tailBlock = (Tail as Block)!;
tailBlock.Instructions.RemoveAt(tailBlock.Instructions.Count - 1);
// Add a new node that is branched to at the end, to keep control flow internal
@@ -79,7 +79,7 @@ public override string ToString()
public override void BuildAST(ASTBuilder builder, List output)
{
// Push this loop context
- Loop prevLoop = builder.TopFragmentContext.SurroundingLoop;
+ Loop? prevLoop = builder.TopFragmentContext!.SurroundingLoop;
builder.TopFragmentContext.SurroundingLoop = this;
// Build body
diff --git a/Underanalyzer/Decompiler/ControlFlow/EmptyNode.cs b/Underanalyzer/Decompiler/ControlFlow/EmptyNode.cs
index e300ee2..62016a1 100644
--- a/Underanalyzer/Decompiler/ControlFlow/EmptyNode.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/EmptyNode.cs
@@ -19,13 +19,13 @@ internal class EmptyNode(int address) : IControlFlowNode
public int EndAddress { get; set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
diff --git a/Underanalyzer/Decompiler/ControlFlow/ExitNode.cs b/Underanalyzer/Decompiler/ControlFlow/ExitNode.cs
index 0812d6e..f82ff55 100644
--- a/Underanalyzer/Decompiler/ControlFlow/ExitNode.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/ExitNode.cs
@@ -18,13 +18,13 @@ internal class ExitNode(int address) : IControlFlowNode
public int EndAddress { get; set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
diff --git a/Underanalyzer/Decompiler/ControlFlow/Fragment.cs b/Underanalyzer/Decompiler/ControlFlow/Fragment.cs
index cc4b21c..13efc22 100644
--- a/Underanalyzer/Decompiler/ControlFlow/Fragment.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/Fragment.cs
@@ -13,39 +13,31 @@ namespace Underanalyzer.Decompiler.ControlFlow;
///
/// Represents a single VM code fragment, used for single function contexts.
///
-internal class Fragment : IControlFlowNode
+internal class Fragment(int startAddr, int endAddr, IGMCode codeEntry, List blocks) : IControlFlowNode
{
- public int StartAddress { get; private set; }
+ public int StartAddress { get; private set; } = startAddr;
- public int EndAddress { get; private set; }
+ public int EndAddress { get; private set; } = endAddr;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; }
+ public List Children { get; } = blocks!;
public bool Unreachable { get; set; } = false;
///
/// Code entry that this fragment belongs to.
///
- public IGMCode CodeEntry { get; }
+ public IGMCode CodeEntry { get; } = codeEntry;
///
/// The base blocks that this fragment is composed of.
///
- public List Blocks { get; } = new();
-
- public Fragment(int startAddr, int endAddr, IGMCode codeEntry, List blocks)
- {
- StartAddress = startAddr;
- EndAddress = endAddr;
- CodeEntry = codeEntry;
- Children = blocks;
- }
+ public List Blocks { get; } = [];
///
/// Finds code fragments from a decompile context.
@@ -53,7 +45,7 @@ public Fragment(int startAddr, int endAddr, IGMCode codeEntry, List
public static List FindFragments(DecompileContext ctx)
{
- List fragments = FindFragments(ctx.Code, ctx.Blocks);
+ List fragments = FindFragments(ctx.Code, ctx.Blocks!);
ctx.FragmentNodes = fragments;
return fragments;
}
@@ -68,7 +60,7 @@ public static List FindFragments(IGMCode code, List blocks)
throw new ArgumentException("Expected code entry to be root level.", nameof(code));
// Map code entry addresses to code entries
- Dictionary codeEntries = new();
+ Dictionary codeEntries = new(code.ChildCount);
for (int i = 0; i < code.ChildCount; i++)
{
IGMCode child = code.GetChild(i);
@@ -76,7 +68,7 @@ public static List FindFragments(IGMCode code, List blocks)
}
// Build fragments, using a stack to track hierarchy
- List fragments = new();
+ List fragments = new(code.ChildCount);
Stack stack = new();
Fragment current = new(code.StartOffset, code.Length, code, []);
fragments.Add(current);
@@ -91,10 +83,10 @@ public static List FindFragments(IGMCode code, List blocks)
{
// We're an inner fragment - mark first block as no longer unreachable, if it is
// (normally always unreachable, unless there's a loop header at the first block)
- if (current.Children[0].Unreachable)
+ if (current.Children[0]!.Unreachable)
{
- current.Children[0].Unreachable = false;
- IControlFlowNode.DisconnectPredecessor(current.Children[0], 0);
+ current.Children[0]!.Unreachable = false;
+ IControlFlowNode.DisconnectPredecessor(current.Children[0]!, 0);
}
// We're an inner fragment - remove "exit" instruction
@@ -124,7 +116,7 @@ public static List FindFragments(IGMCode code, List blocks)
}
// Check for new fragment starting at this block
- if (codeEntries.TryGetValue(block.StartAddress, out IGMCode newCode))
+ if (codeEntries.TryGetValue(block.StartAddress, out IGMCode? newCode))
{
// Our "current" is now the next level up
stack.Push(current);
diff --git a/Underanalyzer/Decompiler/ControlFlow/Loop.cs b/Underanalyzer/Decompiler/ControlFlow/Loop.cs
index 0718330..222a97c 100644
--- a/Underanalyzer/Decompiler/ControlFlow/Loop.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/Loop.cs
@@ -12,31 +12,25 @@ namespace Underanalyzer.Decompiler.ControlFlow;
///
/// Represents a loop (jump/branch backwards) node in a control flow graph.
///
-internal abstract class Loop : IControlFlowNode
+internal abstract class Loop(int startAddress, int endAddress) : IControlFlowNode
{
- public int StartAddress { get; private set; }
+ public int StartAddress { get; private set; } = startAddress;
- public int EndAddress { get; private set; }
+ public int EndAddress { get; private set; } = endAddress;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
///
/// The child nodes of this loop, those being the constituent parts (such as loop head, tail, and so on).
///
- public abstract List Children { get; }
+ public abstract List Children { get; }
public bool Unreachable { get; set; } = false;
- public Loop(int startAddress, int endAddress)
- {
- StartAddress = startAddress;
- EndAddress = endAddress;
- }
-
///
/// Called to insert a given loop's node into the control flow graph.
///
@@ -47,10 +41,10 @@ public Loop(int startAddress, int endAddress)
///
public static List FindLoops(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
+ List blocks = ctx.Blocks!;
- List loops = new();
- HashSet whileLoopsFound = new();
+ List loops = [];
+ HashSet whileLoopsFound = [];
// Search for different loop types based on instruction patterns
// Do this in reverse order, because we want to find the ends of loops first
@@ -60,7 +54,9 @@ public static List FindLoops(DecompileContext ctx)
// If empty, we don't care about the block
if (block.Instructions.Count == 0)
+ {
continue;
+ }
// Check last instruction (where branches are located)
IGMInstruction instr = block.Instructions[^1];
@@ -98,8 +94,8 @@ public static List FindLoops(DecompileContext ctx)
case IGMInstruction.Opcode.PushWithContext:
{
// With loop detected - need to additionally check for break block
- Block afterBlock = block.Successors[1].Successors[0] as Block;
- Block breakBlock = null;
+ Block afterBlock = (block.Successors[1].Successors[0] as Block)!;
+ Block? breakBlock = null;
if (afterBlock.Instructions is [{ Kind: IGMInstruction.Opcode.Branch }])
{
Block potentialBreakBlock = blocks[afterBlock.BlockIndex + 1];
diff --git a/Underanalyzer/Decompiler/ControlFlow/Nullish.cs b/Underanalyzer/Decompiler/ControlFlow/Nullish.cs
index f236f43..2133bb3 100644
--- a/Underanalyzer/Decompiler/ControlFlow/Nullish.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/Nullish.cs
@@ -21,13 +21,13 @@ public enum NullishType
public int EndAddress { get; private set; }
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = [null];
+ public List Children { get; } = [null];
public bool Unreachable { get; set; } = false;
@@ -40,7 +40,7 @@ public enum NullishType
/// Upon being processed, this has its predecessors disconnected.
/// All paths exiting from it are also isolated from the external graph.
///
- public IControlFlowNode IfNullish { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode IfNullish { get => Children[0]!; private set => Children[0] = value; }
public Nullish(int startAddress, int endAddress, NullishType nullishKind, IControlFlowNode ifNullishNode)
{
@@ -55,9 +55,9 @@ public Nullish(int startAddress, int endAddress, NullishType nullishKind, IContr
///
public static List FindNullish(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
+ List blocks = ctx.Blocks!;
- List res = new();
+ List res = [];
for (int j = blocks.Count - 1; j >= 0; j--)
{
@@ -69,8 +69,8 @@ public static List FindNullish(DecompileContext ctx)
{ Kind: IGMInstruction.Opcode.BranchFalse }
])
{
- Block ifNullishBlock = block.Successors[0] as Block;
- Block afterBlock = block.Successors[1] as Block;
+ Block ifNullishBlock = block.Successors[0] as Block ?? throw new DecompilerException("Expected first successor to be block");
+ Block afterBlock = block.Successors[1] as Block ?? throw new DecompilerException("Expected second successor to be block");
// Determine nullish type by using the block "after"
NullishType nullishKind = NullishType.Expression;
@@ -88,7 +88,7 @@ public static List FindNullish(DecompileContext ctx)
// Remove pop instruction from "if nullish" block
ifNullishBlock.Instructions.RemoveAt(0);
- Block endOfNullishBlock = null;
+ Block? endOfNullishBlock = null;
if (nullishKind == NullishType.Assignment)
{
// Remove pop instruction from "after" block
diff --git a/Underanalyzer/Decompiler/ControlFlow/RepeatLoop.cs b/Underanalyzer/Decompiler/ControlFlow/RepeatLoop.cs
index a3513d8..294863b 100644
--- a/Underanalyzer/Decompiler/ControlFlow/RepeatLoop.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/RepeatLoop.cs
@@ -14,7 +14,7 @@ namespace Underanalyzer.Decompiler.ControlFlow;
///
internal class RepeatLoop : Loop
{
- public override List Children { get; } = [null, null, null];
+ public override List Children { get; } = [null, null, null];
///
/// The top loop point and body of the loop, as written in the source code.
@@ -23,7 +23,7 @@ internal class RepeatLoop : Loop
/// Upon being processed, this has its predecessors disconnected.
/// Of its predecessors, the instructions used to initialize the loop counter are removed.
///
- public IControlFlowNode Head { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Head { get => Children[0]!; private set => Children[0] = value; }
///
/// The bottom loop point of the loop, where the loop counter is decremented.
@@ -31,7 +31,7 @@ internal class RepeatLoop : Loop
///
/// Upon being processed, all instructions pertaining to the loop counter are removed, and all successors are disconnected.
///
- public IControlFlowNode Tail { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode Tail { get => Children[1]!; private set => Children[1] = value; }
///
/// The "sink" location of the loop. The loop counter being falsey or "break" statements will lead to this location.
@@ -40,7 +40,7 @@ internal class RepeatLoop : Loop
/// Upon being processed, this becomes a new , which is then disconnected from the external graph.
/// Additionally, a final pop instruction is removed.
///
- public IControlFlowNode After { get => Children[2]; private set => Children[2] = value; }
+ public IControlFlowNode After { get => Children[2]!; private set => Children[2] = value; }
public RepeatLoop(int startAddress, int endAddress, IControlFlowNode head, IControlFlowNode tail, IControlFlowNode after)
: base(startAddress, endAddress)
@@ -54,14 +54,14 @@ public override void UpdateFlowGraph()
{
// Get rid of branch (and unneeded logic) from branch into Head
// The (first) predecessor of Head should always be a Block, as it has logic
- Block headPred = Head.Predecessors[0] as Block;
+ Block headPred = Head.Predecessors[0] as Block ?? throw new DecompilerException("Expected first predecessor to be block");
headPred.Instructions.RemoveRange(headPred.Instructions.Count - 4, 4);
IControlFlowNode.DisconnectSuccessor(headPred, 1);
// Get rid of jumps (and unneeded logic) from Tail
IControlFlowNode.DisconnectSuccessor(Tail, 1);
IControlFlowNode.DisconnectSuccessor(Tail, 0);
- Block tailBlock = Tail as Block;
+ Block tailBlock = Tail as Block ?? throw new DecompilerException("Expected Tail to be block");
if (tailBlock.Instructions is
[.., { Kind: IGMInstruction.Opcode.Convert }, { Kind: IGMInstruction.Opcode.BranchTrue }])
{
@@ -75,7 +75,7 @@ public override void UpdateFlowGraph()
}
// Remove unneeded logic from After (should also always be a Block)
- Block afterBlock = After as Block;
+ Block afterBlock = After as Block ?? throw new DecompilerException("Expected After to be block");
afterBlock.Instructions.RemoveAt(0);
// Add a new node that is branched to at the end, to keep control flow internal
@@ -104,7 +104,7 @@ public override void BuildAST(ASTBuilder builder, List output)
IExpressionNode timesToRepeat = builder.ExpressionStack.Pop();
// Push this loop context
- Loop prevLoop = builder.TopFragmentContext.SurroundingLoop;
+ Loop? prevLoop = builder.TopFragmentContext!.SurroundingLoop;
builder.TopFragmentContext.SurroundingLoop = this;
// Build loop body, and create statement
diff --git a/Underanalyzer/Decompiler/ControlFlow/ReturnNode.cs b/Underanalyzer/Decompiler/ControlFlow/ReturnNode.cs
index 38ec48c..bd5c09f 100644
--- a/Underanalyzer/Decompiler/ControlFlow/ReturnNode.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/ReturnNode.cs
@@ -18,13 +18,13 @@ internal class ReturnNode(int address) : IControlFlowNode
public int EndAddress { get; set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
diff --git a/Underanalyzer/Decompiler/ControlFlow/ShortCircuit.cs b/Underanalyzer/Decompiler/ControlFlow/ShortCircuit.cs
index 4796d64..da2e2c3 100644
--- a/Underanalyzer/Decompiler/ControlFlow/ShortCircuit.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/ShortCircuit.cs
@@ -15,41 +15,34 @@ public enum ShortCircuitType
Or
}
-internal class ShortCircuit : IControlFlowNode
+internal class ShortCircuit(int startAddress, int endAddress, ShortCircuitType logicKind, List children)
+ : IControlFlowNode
{
- public int StartAddress { get; private set; }
+ public int StartAddress { get; private set; } = startAddress;
- public int EndAddress { get; private set; }
+ public int EndAddress { get; private set; } = endAddress;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = children!;
public bool Unreachable { get; set; } = false;
- public ShortCircuitType LogicKind { get; }
-
- public ShortCircuit(int startAddress, int endAddress, ShortCircuitType logicKind, List children)
- {
- StartAddress = startAddress;
- EndAddress = endAddress;
- LogicKind = logicKind;
- Children = children;
- }
+ public ShortCircuitType LogicKind { get; } = logicKind;
///
/// Locates all blocks where a short circuit "ends", storing them on the context for later processing.
///
public static void FindShortCircuits(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
+ List blocks = ctx.Blocks!;
bool oldBytecodeVersion = ctx.OlderThanBytecode15;
- ctx.ShortCircuitBlocks = new();
+ ctx.ShortCircuitBlocks = [];
// Identify and restructure short circuits
foreach (var block in blocks)
@@ -85,10 +78,10 @@ block is
///
public static List InsertShortCircuits(DecompileContext ctx)
{
- List shortCircuits = new();
+ List shortCircuits = [];
// Identify and restructure short circuits
- foreach (var block in ctx.ShortCircuitBlocks)
+ foreach (var block in ctx.ShortCircuitBlocks!)
{
// Add child nodes
List children = [block.Predecessors[0]];
@@ -106,12 +99,12 @@ public static List InsertShortCircuits(DecompileContext ctx)
// Remove branches and connections from previous blocks (not necessarily children!)
for (int i = block.Predecessors.Count - 1; i >= 0; i--)
{
- Block pred = block.Predecessors[i] as Block;
+ Block pred = block.Predecessors[i] as Block ?? throw new DecompilerException("Expected predecessor to be block");
pred.Instructions.RemoveAt(pred.Instructions.Count - 1);
IControlFlowNode.DisconnectSuccessor(pred, 1);
IControlFlowNode.DisconnectSuccessor(pred, 0);
}
- Block finalBlock = ctx.Blocks[block.BlockIndex - 1];
+ Block finalBlock = ctx.Blocks![block.BlockIndex - 1];
finalBlock.Instructions.RemoveAt(finalBlock.Instructions.Count - 1);
IControlFlowNode.DisconnectSuccessor(finalBlock, 0);
@@ -150,8 +143,7 @@ public void BuildAST(ASTBuilder builder, List output)
// Build the rest of the conditions
for (int i = 1; i < Children.Count; i++)
{
- IControlFlowNode child = Children[i];
- conditions.Add(builder.BuildExpression(child));
+ conditions.Add(builder.BuildExpression(Children[i]));
}
builder.ExpressionStack.Push(new ShortCircuitNode(conditions, LogicKind));
diff --git a/Underanalyzer/Decompiler/ControlFlow/StaticInit.cs b/Underanalyzer/Decompiler/ControlFlow/StaticInit.cs
index 98407c8..d2f1d03 100644
--- a/Underanalyzer/Decompiler/ControlFlow/StaticInit.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/StaticInit.cs
@@ -19,13 +19,13 @@ internal class StaticInit : IControlFlowNode
public int EndAddress { get; private set; }
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = [null];
+ public List Children { get; } = [null];
public bool Unreachable { get; set; } = false;
@@ -35,7 +35,7 @@ internal class StaticInit : IControlFlowNode
///
/// Upon being processed, this has its predecessors disconnected.
///
- public IControlFlowNode Head { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Head { get => Children[0]!; private set => Children[0] = value; }
public StaticInit(int startAddress, int endAddress, IControlFlowNode head)
{
@@ -49,9 +49,9 @@ public StaticInit(int startAddress, int endAddress, IControlFlowNode head)
///
public static List FindStaticInits(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
+ List blocks = ctx.Blocks!;
- List res = new();
+ List res = [];
foreach (var block in blocks)
{
diff --git a/Underanalyzer/Decompiler/ControlFlow/Switch.cs b/Underanalyzer/Decompiler/ControlFlow/Switch.cs
index 28c1d04..ddd0d31 100644
--- a/Underanalyzer/Decompiler/ControlFlow/Switch.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/Switch.cs
@@ -14,15 +14,15 @@ internal class Switch : IControlFlowNode
///
/// Initial detection data for a switch statement, used to prevent calculations being done twice.
///
- public class SwitchDetectionData
+ public class SwitchDetectionData(Block endBlock, IControlFlowNode endNode, bool mayBeMisdetected)
{
- public Block EndBlock { get; set; } = null;
- public IControlFlowNode EndNode { get; set; } = null;
- public Block ContinueBlock { get; set; } = null;
- public Block ContinueSkipBlock { get; set; } = null;
- public Block EndOfCaseBlock { get; set; } = null;
- public Block DefaultBranchBlock { get; set; } = null;
- public bool MayBeMisdetected { get; set; } = false;
+ public Block EndBlock { get; set; } = endBlock;
+ public IControlFlowNode EndNode { get; set; } = endNode;
+ public Block? ContinueBlock { get; set; } = null;
+ public Block? ContinueSkipBlock { get; set; } = null;
+ public Block? EndOfCaseBlock { get; set; } = null;
+ public Block? DefaultBranchBlock { get; set; } = null;
+ public bool MayBeMisdetected { get; set; } = mayBeMisdetected;
}
public class CaseJumpNode(int address) : IControlFlowNode
@@ -31,13 +31,13 @@ public class CaseJumpNode(int address) : IControlFlowNode
public int EndAddress { get; private set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
@@ -49,7 +49,7 @@ public override string ToString()
public void BuildAST(ASTBuilder builder, List output)
{
// Queue our expression to be used later, when the case destination is processed
- builder.SwitchCases.Enqueue(builder.ExpressionStack.Pop());
+ builder.SwitchCases!.Enqueue(builder.ExpressionStack.Pop());
// Get rid of duplicated expression
builder.ExpressionStack.Pop();
@@ -62,13 +62,13 @@ public class CaseDestinationNode(int address) : IControlFlowNode
public int EndAddress { get; private set; } = address;
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = new();
+ public List Children { get; } = [];
public bool Unreachable { get; set; } = false;
@@ -89,7 +89,7 @@ public void BuildAST(ASTBuilder builder, List output)
else
{
// Retrieve expression from earlier evaluation
- output.Add(new SwitchCaseNode(builder.SwitchCases.Dequeue()));
+ output.Add(new SwitchCaseNode(builder.SwitchCases!.Dequeue()));
}
}
}
@@ -98,31 +98,31 @@ public void BuildAST(ASTBuilder builder, List output)
public int EndAddress { get; private set; }
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = [null, null, null];
+ public List Children { get; } = [null, null, null];
public bool Unreachable { get; set; } = false;
///
/// The first block that begins the chain of case conditions. Should always be a Block.
///
- public IControlFlowNode Cases { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Cases { get => Children[0] ?? throw new System.NullReferenceException(); private set => Children[0] = value; }
///
- /// The first node of the switch statement body. Should always be a CaseDestinationNode, or null.
+ /// The first node of the switch statement body. Should always be a CaseDestinationNode, or .
///
- public IControlFlowNode Body { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode? Body { get => Children[1]; private set => Children[1] = value; }
///
- /// An optional successor chain of case destinations (null if none was necessary).
+ /// An optional successor chain of case destinations ( if none was necessary).
/// Specifically, those that appear at the very end of the switch statement and have no code.
///
- public IControlFlowNode EndCaseDestinations { get => Children[2]; private set => Children[2] = value; }
+ public IControlFlowNode? EndCaseDestinations { get => Children[2]; private set => Children[2] = value; }
///
/// The data used to detect this switch statement, used for later verification.
@@ -130,7 +130,7 @@ public void BuildAST(ASTBuilder builder, List output)
internal SwitchDetectionData DetectionData { get; }
public Switch(int startAddress, int endAddress,
- IControlFlowNode cases, IControlFlowNode body, IControlFlowNode endCaseDestinations, SwitchDetectionData data)
+ IControlFlowNode cases, IControlFlowNode? body, IControlFlowNode? endCaseDestinations, SwitchDetectionData data)
{
StartAddress = startAddress;
EndAddress = endAddress;
@@ -144,17 +144,21 @@ private class BlockIndexComparer : IComparer
{
public static BlockIndexComparer Instance { get; } = new();
- public int Compare(Block x, Block y)
+ public int Compare(Block? x, Block? y)
{
+ if (x is null || y is null)
+ {
+ throw new System.NullReferenceException();
+ }
return x.BlockIndex - y.BlockIndex;
}
}
private static void DetectionPass(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
- List fragments = ctx.FragmentNodes;
- ctx.BlockSurroundingLoops ??= Branches.FindSurroundingLoops(blocks, ctx.BlocksByAddress, ctx.LoopNodes);
+ List blocks = ctx.Blocks!;
+ List fragments = ctx.FragmentNodes!;
+ ctx.BlockSurroundingLoops ??= Branches.FindSurroundingLoops(blocks, ctx.BlocksByAddress!, ctx.LoopNodes!);
ctx.BlockAfterLimits ??= Branches.ComputeBlockAfterLimits(blocks, ctx.BlockSurroundingLoops);
foreach (Fragment fragment in fragments)
@@ -162,9 +166,9 @@ private static void DetectionPass(DecompileContext ctx)
for (int i = 0; i < fragment.Blocks.Count - 1; i++)
{
Block block = fragment.Blocks[i];
- Block endCaseBlock = null;
- IControlFlowNode endNode = null;
- Block endBlock = null;
+ Block? endCaseBlock = null;
+ IControlFlowNode? endNode = null;
+ Block? endBlock = null;
bool mayBeMisdetected = false;
if (block.Instructions is [.., { Kind: IGMInstruction.Opcode.BranchTrue }])
{
@@ -197,7 +201,7 @@ private static void DetectionPass(DecompileContext ctx)
// If there exists a default case branch in this switch statement, it would be this one.
// To check, we look at the next block (at least one more *should* exist) to see if it's an unreachable Branch block.
// We use this to determine endNode.
- Block firstBranchBlock = node as Block;
+ Block firstBranchBlock = node as Block ?? throw new System.NullReferenceException();
Block nextBlock = blocks[firstBranchBlock.BlockIndex + 1];
if (nextBlock is { Unreachable: true, Instructions: [{ Kind: IGMInstruction.Opcode.Branch }] })
{
@@ -210,7 +214,7 @@ private static void DetectionPass(DecompileContext ctx)
endCaseBlock = firstBranchBlock;
}
endNode = endCaseBlock.Successors[0];
- endBlock = ctx.BlocksByAddress[endNode.StartAddress];
+ endBlock = ctx.BlocksByAddress![endNode.StartAddress];
}
else if (block.Instructions is [.., { Kind: IGMInstruction.Opcode.Branch }])
{
@@ -221,7 +225,7 @@ private static void DetectionPass(DecompileContext ctx)
// nextBlock is the end of case block
endCaseBlock = nextBlock;
endNode = nextBlock.Successors[0];
- endBlock = ctx.BlocksByAddress[endNode.StartAddress];
+ endBlock = ctx.BlocksByAddress![endNode.StartAddress];
// Ensure both branches are forward branches (past these two blocks)
if (block.Successors[0].StartAddress < nextBlock.EndAddress ||
@@ -313,12 +317,17 @@ private static void DetectionPass(DecompileContext ctx)
if (endNode is not null)
{
+ if (endBlock is null || endCaseBlock is null)
+ {
+ throw new System.NullReferenceException();
+ }
+
// Check that we're not detecting the same switch twice (e.g. due to a break/continue statement)
- if (ctx.SwitchEndNodes.Contains(endNode))
+ if (ctx.SwitchEndNodes!.Contains(endNode))
{
continue;
}
- if (ctx.SwitchContinueBlocks.Contains(endBlock))
+ if (ctx.SwitchContinueBlocks!.Contains(endBlock))
{
continue;
}
@@ -327,13 +336,8 @@ private static void DetectionPass(DecompileContext ctx)
ctx.SwitchEndNodes.Add(endNode);
// Create detection data
- SwitchDetectionData data = new()
- {
- EndBlock = endBlock,
- EndNode = endNode,
- MayBeMisdetected = mayBeMisdetected
- };
- ctx.SwitchData.Add(data);
+ SwitchDetectionData data = new(endBlock, endNode, mayBeMisdetected);
+ ctx.SwitchData!.Add(data);
// Update index for next iteration (to be after the end case node's block index).
int endCaseBlockIndex =
@@ -369,7 +373,7 @@ private static void DetectionPass(DecompileContext ctx)
// This is definitely a switch continue block
ctx.SwitchContinueBlocks.Add(previousBlock);
- ctx.SwitchIgnoreJumpBlocks.Add(previousPreviousBlock);
+ ctx.SwitchIgnoreJumpBlocks!.Add(previousPreviousBlock);
data.ContinueBlock = previousBlock;
data.ContinueSkipBlock = previousPreviousBlock;
}
@@ -379,12 +383,12 @@ private static void DetectionPass(DecompileContext ctx)
private static void DetailPass(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
+ List blocks = ctx.Blocks!;
- foreach (SwitchDetectionData data in ctx.SwitchData)
+ foreach (SwitchDetectionData data in ctx.SwitchData!)
{
// Find first predecessor that ends in Branch (should be the first one that *doesn't* end in BranchTrue)
- Block firstBranchPredecessor = null;
+ Block? firstBranchPredecessor = null;
foreach (IControlFlowNode pred in data.EndNode.Predecessors)
{
if (pred is Block predBlock && predBlock.Instructions is [.., { Kind: IGMInstruction.Opcode.Branch }])
@@ -393,7 +397,7 @@ private static void DetailPass(DecompileContext ctx)
break;
}
}
- if (firstBranchPredecessor == null)
+ if (firstBranchPredecessor is null)
{
throw new DecompilerException("Failed to find end of switch cases");
}
@@ -409,7 +413,7 @@ private static void DetailPass(DecompileContext ctx)
// - Otherwise, there's no default branch
data.EndOfCaseBlock = firstBranchPredecessor;
bool prevBlockIsDefaultBranch;
- if (firstBranchPredecessor.BlockIndex >= 1 && !ctx.SwitchEndNodes.Contains(firstBranchPredecessor))
+ if (firstBranchPredecessor.BlockIndex >= 1 && !ctx.SwitchEndNodes!.Contains(firstBranchPredecessor))
{
Block prevBlock = blocks[firstBranchPredecessor.BlockIndex - 1];
if (prevBlock.Instructions is not [.., { Kind: IGMInstruction.Opcode.Branch }])
@@ -442,7 +446,7 @@ private static void DetailPass(DecompileContext ctx)
}
// Update list of blocks that we should ignore
- ctx.SwitchIgnoreJumpBlocks.Add(data.EndOfCaseBlock);
+ ctx.SwitchIgnoreJumpBlocks!.Add(data.EndOfCaseBlock);
if (data.DefaultBranchBlock is not null)
{
ctx.SwitchIgnoreJumpBlocks.Add(data.DefaultBranchBlock);
@@ -457,10 +461,10 @@ private static void DetailPass(DecompileContext ctx)
///
public static void FindSwitchStatements(DecompileContext ctx)
{
- ctx.SwitchEndNodes = new();
- ctx.SwitchData = new();
- ctx.SwitchContinueBlocks = new();
- ctx.SwitchIgnoreJumpBlocks = new();
+ ctx.SwitchEndNodes = [];
+ ctx.SwitchData = [];
+ ctx.SwitchContinueBlocks = [];
+ ctx.SwitchIgnoreJumpBlocks = [];
// First pass: simply detect the end blocks of switch statements, as well as continue blocks.
// We do this first as this requires a special algorithm to prevent false positives/negatives.
@@ -477,15 +481,15 @@ public static void FindSwitchStatements(DecompileContext ctx)
///
public static List InsertSwitchStatements(DecompileContext ctx)
{
- List res = new();
+ List res = [];
- for (int j = 0; j < ctx.SwitchData.Count; j++)
+ for (int j = 0; j < ctx.SwitchData!.Count; j++)
{
SwitchDetectionData data = ctx.SwitchData[j];
// Find all cases
- IControlFlowNode currentNode = data.EndOfCaseBlock;
- List caseBranches = new();
+ IControlFlowNode? currentNode = data.EndOfCaseBlock;
+ List caseBranches = [];
while (currentNode is not null)
{
if (currentNode is Block currentBlock)
@@ -496,7 +500,7 @@ public static List InsertSwitchStatements(DecompileContext ctx)
caseBranches.Add(currentBlock);
}
- if (ctx.SwitchEndNodes.Contains(currentBlock))
+ if (ctx.SwitchEndNodes!.Contains(currentBlock))
{
// We're at the end of another switch statement - do not continue
break;
@@ -515,10 +519,10 @@ public static List InsertSwitchStatements(DecompileContext ctx)
// Update graph for all cases (in reverse; we found them backwards)
// First pass: update chain of conditions
- IControlFlowNode startOfBody = null;
- IControlFlowNode endCaseDestinations = null;
- IControlFlowNode endCaseDestinationsEnd = null;
- List caseDestinationNodes = new();
+ IControlFlowNode? startOfBody = null;
+ IControlFlowNode? endCaseDestinations = null;
+ IControlFlowNode? endCaseDestinationsEnd = null;
+ List caseDestinationNodes = new(caseBranches.Count);
for (int i = caseBranches.Count - 1; i >= 0; i--)
{
Block currentBlock = caseBranches[i];
@@ -540,7 +544,7 @@ public static List InsertSwitchStatements(DecompileContext ctx)
}
}
// First pass (part two): also update default case
- IControlFlowNode defaultDestinationNode = null;
+ IControlFlowNode? defaultDestinationNode = null;
if (data.DefaultBranchBlock is not null)
{
Block defaultBlock = data.DefaultBranchBlock;
@@ -568,7 +572,7 @@ public static List InsertSwitchStatements(DecompileContext ctx)
}
else
{
- endCaseDestinationsEnd.Successors.Add(caseDestNode);
+ endCaseDestinationsEnd!.Successors.Add(caseDestNode);
caseDestNode.Predecessors.Add(endCaseDestinationsEnd);
endCaseDestinationsEnd = caseDestNode;
}
@@ -605,9 +609,8 @@ public static List InsertSwitchStatements(DecompileContext ctx)
}
else
{
- endCaseDestinationsEnd.Successors.Add(caseDestNode);
+ endCaseDestinationsEnd!.Successors.Add(caseDestNode);
caseDestNode.Predecessors.Add(endCaseDestinationsEnd);
- endCaseDestinationsEnd = caseDestNode;
}
}
else
@@ -650,7 +653,7 @@ public static List InsertSwitchStatements(DecompileContext ctx)
IControlFlowNode.DisconnectSuccessor(continueBlock, i);
}
- Block skipContinueBlock = data.ContinueSkipBlock;
+ Block skipContinueBlock = data.ContinueSkipBlock!;
skipContinueBlock.Instructions.RemoveAt(skipContinueBlock.Instructions.Count - 1);
for (int i = skipContinueBlock.Predecessors.Count - 1; i >= 0; i--)
{
@@ -675,7 +678,7 @@ public static List InsertSwitchStatements(DecompileContext ctx)
}
// Construct actual switch node
- Block startOfStatement = (caseBranches.Count > 0) ? caseBranches[^1] : (data.DefaultBranchBlock ?? data.EndOfCaseBlock);
+ Block startOfStatement = (caseBranches.Count > 0) ? caseBranches[^1] : (data.DefaultBranchBlock ?? data.EndOfCaseBlock)!;
Switch switchNode =
new(startOfStatement.StartAddress, endNode.StartAddress, startOfStatement, startOfBody, endCaseDestinations, data);
IControlFlowNode.InsertStructure(startOfStatement, endNode, switchNode);
@@ -724,10 +727,10 @@ public void BuildAST(ASTBuilder builder, List output)
{
endNode = endNode.Parent;
}
- bool forward = (endNode.StartAddress > DetectionData.EndOfCaseBlock.EndAddress - 4);
+ bool forward = (endNode.StartAddress > DetectionData.EndOfCaseBlock!.EndAddress - 4);
// Now, we check our surrounding loop to see if it needs to be transformed between while/for.
- Loop loop = builder.TopFragmentContext.SurroundingLoop;
+ Loop? loop = builder.TopFragmentContext!.SurroundingLoop;
if (loop is WhileLoop whileLoop)
{
if (forward)
diff --git a/Underanalyzer/Decompiler/ControlFlow/TryCatch.cs b/Underanalyzer/Decompiler/ControlFlow/TryCatch.cs
index ad14a54..b9f32d2 100644
--- a/Underanalyzer/Decompiler/ControlFlow/TryCatch.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/TryCatch.cs
@@ -19,13 +19,13 @@ internal class TryCatch : IControlFlowNode
public int EndAddress { get; private set; }
- public List Predecessors { get; } = new();
+ public List Predecessors { get; } = [];
- public List Successors { get; } = new();
+ public List Successors { get; } = [];
- public IControlFlowNode Parent { get; set; } = null;
+ public IControlFlowNode? Parent { get; set; } = null;
- public List Children { get; } = [null, null];
+ public List Children { get; } = [null, null];
public bool Unreachable { get; set; } = false;
@@ -36,18 +36,18 @@ internal class TryCatch : IControlFlowNode
/// Upon being processed, this has its predecessors disconnected.
/// All paths exiting from it are also isolated from the external graph.
///
- public IControlFlowNode Try { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Try { get => Children[0]!; private set => Children[0] = value; }
///
- /// The "catch" block of the try statement, or null if none exists.
+ /// The "catch" block of the try statement, or if none exists.
///
///
/// Upon being processed, this has its predecessors disconnected.
/// All paths exiting from it are also isolated from the external graph.
///
- public IControlFlowNode Catch { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode? Catch { get => Children[1]; private set => Children[1] = value; }
- public TryCatch(int startAddress, int endAddress, IControlFlowNode tryNode, IControlFlowNode catchNode)
+ public TryCatch(int startAddress, int endAddress, IControlFlowNode tryNode, IControlFlowNode? catchNode)
{
StartAddress = startAddress;
EndAddress = endAddress;
@@ -60,9 +60,9 @@ public TryCatch(int startAddress, int endAddress, IControlFlowNode tryNode, ICon
///
public static List FindTryCatch(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
+ List blocks = ctx.Blocks!;
- List res = new();
+ List res = [];
foreach (var block in blocks)
{
@@ -71,14 +71,14 @@ public static List FindTryCatch(DecompileContext ctx)
{
IGMInstruction call = block.Instructions[^2];
if (call.Kind == IGMInstruction.Opcode.Call &&
- call.Function.Name?.Content == VMConstants.TryHookFunction)
+ call.Function?.Name?.Content == VMConstants.TryHookFunction)
{
// Get components of our try..catch statement
IControlFlowNode tryNode = block.Successors[0];
- IControlFlowNode catchNode = block.Successors.Count >= 3 ? block.Successors[2] : null;
- IControlFlowNode endNode = block.Successors[1];
+ IControlFlowNode? catchNode = block.Successors.Count >= 3 ? block.Successors[2] : null;
+ Block endBlock = block.Successors[1] as Block ?? throw new DecompilerException("Expected second successor to be block");
- TryCatch tc = new(block.StartAddress, endNode.StartAddress, tryNode, catchNode);
+ TryCatch tc = new(block.StartAddress, endBlock.StartAddress, tryNode, catchNode);
res.Add(tc);
// Remove predecessor of try node
@@ -91,7 +91,7 @@ public static List FindTryCatch(DecompileContext ctx)
// Remove branch instruction from end node's second predecessor, i.e.
// the end of the try block
- Block tryEndBlock = endNode.Predecessors[1] as Block;
+ Block tryEndBlock = endBlock.Predecessors[1] as Block ?? throw new DecompilerException("Expected second predecessor to be block");
if (tryEndBlock == block || tryEndBlock.StartAddress >= catchNode.StartAddress)
{
throw new DecompilerException("Failed to find end of try block");
@@ -104,7 +104,7 @@ public static List FindTryCatch(DecompileContext ctx)
tryEndBlock.Instructions.RemoveAt(tryEndBlock.Instructions.Count - 1);
// Remove instructions from the end of the catch block
- Block catchEndBlock = blocks[(endNode as Block).BlockIndex - 1];
+ Block catchEndBlock = blocks[endBlock.BlockIndex - 1];
if (catchEndBlock.Instructions is not
[.., { Kind: IGMInstruction.Opcode.Call }, { Kind: IGMInstruction.Opcode.PopDelete },
{ Kind: IGMInstruction.Opcode.Branch }])
@@ -115,16 +115,16 @@ public static List FindTryCatch(DecompileContext ctx)
// Reroute end of the catch block into our end node (temporarily)
IControlFlowNode.DisconnectSuccessor(catchEndBlock, 0);
- catchEndBlock.Successors.Add(endNode);
- endNode.Predecessors.Add(catchEndBlock);
+ catchEndBlock.Successors.Add(endBlock);
+ endBlock.Predecessors.Add(catchEndBlock);
}
// Disconnect start node from end node
- IControlFlowNode.DisconnectPredecessor(endNode, 0);
+ IControlFlowNode.DisconnectPredecessor(endBlock, 0);
// Add new empty node to act as a meet point for both try and catch blocks
- EmptyNode empty = new(endNode.StartAddress);
- IControlFlowNode.InsertPredecessors(endNode, empty, tc.StartAddress);
+ EmptyNode empty = new(endBlock.StartAddress);
+ IControlFlowNode.InsertPredecessors(endBlock, empty, tc.StartAddress);
// Disconnect new empty node from the end node
IControlFlowNode.DisconnectSuccessor(empty, 0);
@@ -133,9 +133,8 @@ public static List FindTryCatch(DecompileContext ctx)
block.Instructions.Clear();
// Remove try unhook instructions from end node
- Block endBlock = endNode as Block;
if (endBlock.Instructions[0].Kind != IGMInstruction.Opcode.Call ||
- endBlock.Instructions[0].Function.Name?.Content != VMConstants.TryUnhookFunction)
+ endBlock.Instructions[0].Function?.Name?.Content != VMConstants.TryUnhookFunction)
{
throw new DecompilerException("Expected try unhook in end node");
}
@@ -148,12 +147,12 @@ public static List FindTryCatch(DecompileContext ctx)
}
block.Successors.Add(tc);
tc.Predecessors.Add(block);
- if (endNode.Predecessors.Count != 0)
+ if (endBlock.Predecessors.Count != 0)
{
throw new DecompilerException("Expected no predecessors for try end block");
}
- tc.Successors.Add(endNode);
- endNode.Predecessors.Add(tc);
+ tc.Successors.Add(endBlock);
+ endBlock.Predecessors.Add(tc);
continue;
}
@@ -164,7 +163,7 @@ public static List FindTryCatch(DecompileContext ctx)
{
IGMInstruction call = block.Instructions[^3];
if (call.Kind == IGMInstruction.Opcode.Call &&
- call.Function.Name?.Content == VMConstants.FinishFinallyFunction)
+ call.Function?.Name?.Content == VMConstants.FinishFinallyFunction)
{
// Remove redundant branch instruction for later operation.
// We leave final blocks for post-processing on the syntax tree due to complexity.
@@ -188,7 +187,7 @@ public static List FindTryCatch(DecompileContext ctx)
///
public static void CleanTryEndBranches(DecompileContext ctx)
{
- foreach (TryCatch tc in ctx.TryCatchNodes)
+ foreach (TryCatch tc in ctx.TryCatchNodes!)
{
// Only process if we have 1 successor (and more than 1 is an error at this point)
if (tc.Successors.Count == 0)
@@ -223,20 +222,17 @@ public void BuildAST(ASTBuilder builder, List output)
BlockNode tryBlock = builder.BuildBlock(tryNode);
// Handle catch block, if it exists
- BlockNode catchBlock = null;
- VariableNode catchVariable = null;
+ BlockNode? catchBlock = null;
+ VariableNode? catchVariable = null;
if (Catch is not null)
{
// Get variable from start of catch's initial block
- Block catchInstrBlock = builder.Context.BlocksByAddress[Catch.StartAddress];
+ Block catchInstrBlock = builder.Context.BlocksByAddress![Catch.StartAddress];
if (catchInstrBlock.Instructions is not [{ Kind: IGMInstruction.Opcode.Pop, Variable: IGMVariable variable }, ..])
{
throw new DecompilerException("Expected first instruction of catch block to store to variable");
}
- catchVariable = new VariableNode(variable, IGMInstruction.VariableType.Normal)
- {
- Left = new InstanceTypeNode(IGMInstruction.InstanceType.Local)
- };
+ catchVariable = new VariableNode(variable, IGMInstruction.VariableType.Normal, new InstanceTypeNode(IGMInstruction.InstanceType.Local));
catchInstrBlock.Instructions.RemoveAt(0);
// Register this as a local variable, but not to local variable declaration list
diff --git a/Underanalyzer/Decompiler/ControlFlow/WhileLoop.cs b/Underanalyzer/Decompiler/ControlFlow/WhileLoop.cs
index 14849bf..7fd44be 100644
--- a/Underanalyzer/Decompiler/ControlFlow/WhileLoop.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/WhileLoop.cs
@@ -15,7 +15,7 @@ namespace Underanalyzer.Decompiler.ControlFlow;
///
internal class WhileLoop : Loop
{
- public override List Children { get; } = [null, null, null, null, null];
+ public override List Children { get; } = [null, null, null, null, null];
///
/// The top loop point of the while loop. This is where the loop condition begins to be evaluated.
@@ -23,7 +23,7 @@ internal class WhileLoop : Loop
///
/// Upon being processed, this becomes disconnected from the rest of the graph.
///
- public IControlFlowNode Head { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Head { get => Children[0]!; private set => Children[0] = value; }
///
/// The bottom loop point of the while loop. This is where the jump back to the loop head/condition is located.
@@ -31,7 +31,7 @@ internal class WhileLoop : Loop
///
/// Upon being processed, this becomes disconnected from the rest of the graph.
///
- public IControlFlowNode Tail { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode Tail { get => Children[1]!; private set => Children[1] = value; }
///
/// The "sink" location of the loop. The loop condition being false or "break" statements will lead to this location.
@@ -39,7 +39,7 @@ internal class WhileLoop : Loop
///
/// Upon being processed, this becomes a new , which is then disconnected from the external graph.
///
- public IControlFlowNode After { get => Children[2]; private set => Children[2] = value; }
+ public IControlFlowNode After { get => Children[2]!; private set => Children[2] = value; }
///
/// The start of the body of the loop, as written in the source code. That is, this does not include the loop condition.
@@ -47,7 +47,7 @@ internal class WhileLoop : Loop
///
/// Upon being processed, this is disconnected from the loop condition (which is otherwise a predecessor).
///
- public IControlFlowNode Body { get => Children[3]; private set => Children[3] = value; }
+ public IControlFlowNode? Body { get => Children[3]; private set => Children[3] = value; }
///
/// If not null, then it was detected that this while loop must be written as a for loop.
@@ -55,7 +55,7 @@ internal class WhileLoop : Loop
/// could not be written using normal if/else statements.
/// This points to the start of the "incrementing" code of the for loop.
///
- public IControlFlowNode ForLoopIncrementor { get => Children[4]; set => Children[4] = value; }
+ public IControlFlowNode? ForLoopIncrementor { get => Children[4]; set => Children[4] = value; }
///
/// If true, this loop was detected to definitively be a while loop.
@@ -75,11 +75,11 @@ public override void UpdateFlowGraph()
{
// Get rid of jump from tail
IControlFlowNode.DisconnectSuccessor(Tail, 0);
- Block tailBlock = Tail as Block;
+ Block tailBlock = Tail as Block ?? throw new DecompilerException("Expected tail to be block");
tailBlock.Instructions.RemoveAt(tailBlock.Instructions.Count - 1);
// Find first branch location after head
- Block branchBlock = null;
+ Block? branchBlock = null;
for (int i = 0; i < After.Predecessors.Count; i++)
{
if (After.Predecessors[i].StartAddress < Head.StartAddress ||
@@ -90,6 +90,10 @@ public override void UpdateFlowGraph()
branchBlock = b;
break;
}
+ if (branchBlock is null)
+ {
+ throw new DecompilerException("Failed to find first branch location after head");
+ }
if (branchBlock.Instructions[^1].Kind != IGMInstruction.Opcode.BranchFalse)
{
throw new DecompilerException("Expected BranchFalse in branch block - misidentified");
@@ -130,7 +134,7 @@ public override void BuildAST(ASTBuilder builder, List output)
IExpressionNode condition = builder.BuildExpression(Head, output);
// Push this loop context
- Loop prevLoop = builder.TopFragmentContext.SurroundingLoop;
+ Loop? prevLoop = builder.TopFragmentContext!.SurroundingLoop;
builder.TopFragmentContext.SurroundingLoop = this;
// Build body and create a for/while loop statement (defaults to while if unknown)
diff --git a/Underanalyzer/Decompiler/ControlFlow/WithLoop.cs b/Underanalyzer/Decompiler/ControlFlow/WithLoop.cs
index 63e47fa..99dcee4 100644
--- a/Underanalyzer/Decompiler/ControlFlow/WithLoop.cs
+++ b/Underanalyzer/Decompiler/ControlFlow/WithLoop.cs
@@ -11,7 +11,7 @@ namespace Underanalyzer.Decompiler.ControlFlow;
internal class WithLoop : Loop
{
- public override List Children { get; } = [null, null, null, null, null];
+ public override List Children { get; } = [null, null, null, null, null];
///
/// The node before this loop; usually a block with after it.
@@ -19,7 +19,7 @@ internal class WithLoop : Loop
///
/// Upon being processed, this is connected to the loop.
///
- public IControlFlowNode Before { get => Children[0]; private set => Children[0] = value; }
+ public IControlFlowNode Before { get => Children[0]!; private set => Children[0] = value; }
///
/// The start of the loop body of the with loop.
@@ -27,7 +27,7 @@ internal class WithLoop : Loop
///
/// Upon being processed, this is disconnected from its predecessors.
///
- public IControlFlowNode Head { get => Children[1]; private set => Children[1] = value; }
+ public IControlFlowNode Head { get => Children[1]!; private set => Children[1] = value; }
///
/// The end of the with loop.
@@ -35,7 +35,7 @@ internal class WithLoop : Loop
///
/// Upon being processed, this is disconnected from its successors.
///
- public IControlFlowNode Tail { get => Children[2]; private set => Children[2] = value; }
+ public IControlFlowNode Tail { get => Children[2]!; private set => Children[2] = value; }
///
/// The node reached after the with loop is completed.
@@ -43,7 +43,7 @@ internal class WithLoop : Loop
///
/// Upon being processed, this becomes a new , which is then disconnected from the external graph.
///
- public IControlFlowNode After { get => Children[3]; private set => Children[3] = value; }
+ public IControlFlowNode After { get => Children[3]!; private set => Children[3] = value; }
///
/// If not null, this is a special block jumped to from within the with statement for "break" statements.
@@ -51,11 +51,11 @@ internal class WithLoop : Loop
///
/// Upon being processed, this node is disconnected from the graph.
///
- public IControlFlowNode BreakBlock { get => Children[4]; private set => Children[4] = value; }
+ public IControlFlowNode? BreakBlock { get => Children[4]; private set => Children[4] = value; }
public WithLoop(int startAddress, int endAddress,
IControlFlowNode before, IControlFlowNode head, IControlFlowNode tail,
- IControlFlowNode after, IControlFlowNode breakBlock)
+ IControlFlowNode after, IControlFlowNode? breakBlock)
: base(startAddress, endAddress)
{
Before = before;
@@ -102,7 +102,7 @@ public override void UpdateFlowGraph()
IControlFlowNode.DisconnectSuccessor(BreakBlock, 0);
// Get rid of branch instruction from oldAfter
- Block oldAfterBlock = oldAfter as Block;
+ Block oldAfterBlock = oldAfter as Block ?? throw new DecompilerException("Expected old after to be block");
oldAfterBlock.Instructions.RemoveAt(oldAfterBlock.Instructions.Count - 1);
// Reroute successor of After to instead go to nodeToEndAt
@@ -164,7 +164,7 @@ public override void BuildAST(ASTBuilder builder, List output)
}
// Push this loop context
- Loop prevLoop = builder.TopFragmentContext.SurroundingLoop;
+ Loop? prevLoop = builder.TopFragmentContext!.SurroundingLoop;
builder.TopFragmentContext.SurroundingLoop = this;
// Build loop body, and create statement
diff --git a/Underanalyzer/Decompiler/DecompileContext.cs b/Underanalyzer/Decompiler/DecompileContext.cs
index 1447c50..2e1ce22 100644
--- a/Underanalyzer/Decompiler/DecompileContext.cs
+++ b/Underanalyzer/Decompiler/DecompileContext.cs
@@ -34,36 +34,36 @@ public class DecompileContext
///
/// Any warnings produced throughout the decompilation process.
///
- public List Warnings { get; } = new();
+ public List Warnings { get; } = [];
// Helpers to refer to data on game context
internal bool OlderThanBytecode15 { get => GameContext.Bytecode14OrLower; }
internal bool GMLv2 { get => GameContext.UsingGMLv2; }
// Data structures used (and re-used) for decompilation, as well as tests
- internal List Blocks { get; set; }
- internal Dictionary BlocksByAddress { get; set; }
- internal List FragmentNodes { get; set; }
- internal List LoopNodes { get; set; }
- internal List ShortCircuitBlocks { get; set; }
- internal List ShortCircuitNodes { get; set; }
- internal List StaticInitNodes { get; set; }
- internal List TryCatchNodes { get; set; }
- internal List NullishNodes { get; set; }
- internal List BinaryBranchNodes { get; set; }
- internal HashSet SwitchEndNodes { get; set; }
- internal List SwitchData { get; set; }
- internal HashSet SwitchContinueBlocks { get; set; }
- internal HashSet SwitchIgnoreJumpBlocks { get; set; }
- internal List SwitchNodes { get; set; }
- internal Dictionary BlockSurroundingLoops { get; set; }
- internal Dictionary BlockAfterLimits { get; set; }
- internal List EnumDeclarations { get; set; } = new();
- internal Dictionary NameToEnumDeclaration { get; set; } = new();
- internal GMEnum UnknownEnumDeclaration { get; set; } = null;
+ internal List? Blocks { get; set; }
+ internal Dictionary? BlocksByAddress { get; set; }
+ internal List? FragmentNodes { get; set; }
+ internal List? LoopNodes { get; set; }
+ internal List? ShortCircuitBlocks { get; set; }
+ internal List? ShortCircuitNodes { get; set; }
+ internal List? StaticInitNodes { get; set; }
+ internal List? TryCatchNodes { get; set; }
+ internal List? NullishNodes { get; set; }
+ internal List? BinaryBranchNodes { get; set; }
+ internal HashSet? SwitchEndNodes { get; set; }
+ internal List? SwitchData { get; set; }
+ internal HashSet? SwitchContinueBlocks { get; set; }
+ internal HashSet? SwitchIgnoreJumpBlocks { get; set; }
+ internal List? SwitchNodes { get; set; }
+ internal Dictionary? BlockSurroundingLoops { get; set; }
+ internal Dictionary? BlockAfterLimits { get; set; }
+ internal List EnumDeclarations { get; set; } = [];
+ internal Dictionary NameToEnumDeclaration { get; set; } = [];
+ internal GMEnum? UnknownEnumDeclaration { get; set; } = null;
internal int UnknownEnumReferenceCount { get; set; } = 0;
- public DecompileContext(IGameContext gameContext, IGMCode code, IDecompileSettings settings = null)
+ public DecompileContext(IGameContext gameContext, IGMCode code, IDecompileSettings? settings = null)
{
GameContext = gameContext;
Code = code;
@@ -75,6 +75,7 @@ internal DecompileContext(IGMCode code)
{
Code = code;
GameContext = new Mock.GameContextMock();
+ Settings = new DecompileSettings();
}
// Solely decompiles control flow from the code entry
diff --git a/Underanalyzer/Decompiler/DecompileSettings.cs b/Underanalyzer/Decompiler/DecompileSettings.cs
index 4a7de60..00a0f01 100644
--- a/Underanalyzer/Decompiler/DecompileSettings.cs
+++ b/Underanalyzer/Decompiler/DecompileSettings.cs
@@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the Mozilla Public
*/
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
namespace Underanalyzer.Decompiler;
@@ -116,7 +117,7 @@ public interface IDecompileSettings
///
/// Base type name for the enum representing all unknown enum values.
- /// Should be a valid enum name in GML, or null if the unknown enum should not be generated/used at all.
+ /// Should be a valid enum name in GML, or if the unknown enum should not be generated/used at all.
///
public string UnknownEnumName { get; }
@@ -145,7 +146,7 @@ public interface IDecompileSettings
/// The resulting value to be printed
/// True if parentheses may be needed around the value when printed (due to spaces or operations)
/// True if a predefined double value is found; false otherwise.
- public bool TryGetPredefinedDouble(double value, out string result, out bool isResultMultiPart);
+ public bool TryGetPredefinedDouble(double value, [MaybeNullWhen(false)] out string result, out bool isResultMultiPart);
}
///
@@ -201,7 +202,7 @@ public class DecompileSettings : IDecompileSettings
{ 0.008333333333333333, "1/120" }
};
- public bool TryGetPredefinedDouble(double value, out string result, out bool isResultMultiPart)
+ public bool TryGetPredefinedDouble(double value, [MaybeNullWhen(false)] out string result, out bool isResultMultiPart)
{
if (SinglePartPredefinedDoubles.TryGetValue(value, out result))
{
diff --git a/Underanalyzer/Decompiler/GameSpecific/GMEnum.cs b/Underanalyzer/Decompiler/GameSpecific/GMEnum.cs
index 581ff99..6e785e4 100644
--- a/Underanalyzer/Decompiler/GameSpecific/GMEnum.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/GMEnum.cs
@@ -98,11 +98,11 @@ public void AddNewValuesFrom(GMEnum other)
}
///
- /// Looks up the value entry for the given value, on this enum, or null if none exists.
+ /// Looks up the value entry for the given value, on this enum, or if none exists.
///
- public GMEnumValue FindValue(long value)
+ public GMEnumValue? FindValue(long value)
{
- if (_valueLookupByValue.TryGetValue(value, out GMEnumValue result))
+ if (_valueLookupByValue.TryGetValue(value, out GMEnumValue? result))
{
return result;
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/GameSpecificRegistry.cs b/Underanalyzer/Decompiler/GameSpecific/GameSpecificRegistry.cs
index 265c3b7..e14a01c 100644
--- a/Underanalyzer/Decompiler/GameSpecific/GameSpecificRegistry.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/GameSpecificRegistry.cs
@@ -26,7 +26,7 @@ public class GameSpecificRegistry
///
public GameSpecificRegistry()
{
- MacroTypes = new();
+ MacroTypes = [];
MacroResolver = new();
NamedArgumentResolver = new();
}
@@ -90,7 +90,7 @@ public bool TypeExists(string name)
public IMacroType FindType(string name)
{
- if (MacroTypes.TryGetValue(name, out IMacroType type))
+ if (MacroTypes.TryGetValue(name, out IMacroType? type))
{
return type;
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/IMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/IMacroType.cs
index 5a373bc..e9a7499 100644
--- a/Underanalyzer/Decompiler/GameSpecific/IMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/IMacroType.cs
@@ -21,9 +21,9 @@ public interface IMacroType
public interface IMacroTypeInt32 : IMacroType
{
///
- /// Resolves the macro type with the given 32-bit int value, or null if there is no resolution.
+ /// Resolves the macro type with the given 32-bit int value, or if there is no resolution.
///
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data);
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data);
}
///
@@ -32,9 +32,9 @@ public interface IMacroTypeInt32 : IMacroType
public interface IMacroTypeInt64 : IMacroType
{
///
- /// Resolves the macro type with the given 64-bit int value, or null if there is no resolution.
+ /// Resolves the macro type with the given 64-bit int value, or if there is no resolution.
///
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data);
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data);
}
///
@@ -43,9 +43,9 @@ public interface IMacroTypeInt64 : IMacroType
public interface IMacroTypeFunctionArgs : IMacroType
{
///
- /// Resolves the macro type with the given AST function, or null if there is no resolution.
+ /// Resolves the macro type with the given AST function, or if there is no resolution.
///
- public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode call);
+ public IFunctionCallNode? Resolve(ASTCleaner cleaner, IFunctionCallNode call);
}
///
@@ -54,9 +54,9 @@ public interface IMacroTypeFunctionArgs : IMacroType
public interface IMacroTypeArrayInit : IMacroType
{
///
- /// Resolves the macro type with the given AST array initialization, or null if there is no resolution.
+ /// Resolves the macro type with the given AST array initialization, or if there is no resolution.
///
- public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit);
+ public ArrayInitNode? Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit);
}
@@ -71,7 +71,7 @@ public interface IMacroTypeConditional : IMacroType
public bool Required { get; }
///
- /// Resolves the macro type with the given AST array initialization, or null if there is no resolution.
+ /// Resolves the macro type with the given AST array initialization, or if there is no resolution.
///
- public IExpressionNode Resolve(ASTCleaner cleaner, IConditionalValueNode node);
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IConditionalValueNode node);
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/IMacroTypeResolver.cs b/Underanalyzer/Decompiler/GameSpecific/IMacroTypeResolver.cs
index dc4ce14..845a01e 100644
--- a/Underanalyzer/Decompiler/GameSpecific/IMacroTypeResolver.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/IMacroTypeResolver.cs
@@ -15,17 +15,17 @@ namespace Underanalyzer.Decompiler.GameSpecific;
public interface IMacroTypeResolver
{
///
- /// Resolves a macro type for a variable name on this resolver, or null if none is found.
+ /// Resolves a macro type for a variable name on this resolver, or if none is found.
///
- public IMacroType ResolveVariableType(ASTCleaner cleaner, string variableName);
+ public IMacroType? ResolveVariableType(ASTCleaner cleaner, string? variableName);
///
- /// Resolves a macro type for a function's arguments on this resolver, or null if none is found.
+ /// Resolves a macro type for a function's arguments on this resolver, or if none is found.
///
- public IMacroType ResolveFunctionArgumentTypes(ASTCleaner cleaner, string functionName);
+ public IMacroType? ResolveFunctionArgumentTypes(ASTCleaner cleaner, string? functionName);
///
- /// Resolves a macro type for a function's return value on this resolver, or null if none is found.
+ /// Resolves a macro type for a function's return value on this resolver, or if none is found.
///
- public IMacroType ResolveReturnValueType(ASTCleaner cleaner, string functionName);
+ public IMacroType? ResolveReturnValueType(ASTCleaner cleaner, string? functionName);
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/ArrayInitMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/ArrayInitMacroTypeConverter.cs
index 743f2e7..ddd95dd 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/ArrayInitMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/ArrayInitMacroTypeConverter.cs
@@ -23,7 +23,7 @@ public static ArrayInitMacroType ReadContents(ref Utf8JsonReader reader, IMacroT
}
reader.Read();
- ArrayInitMacroType res = new(macroTypeConverter.Read(ref reader, null, options));
+ ArrayInitMacroType res = new(macroTypeConverter.Read(ref reader, typeof(IMacroType), options) ?? throw new JsonException());
reader.Read();
if (reader.TokenType != JsonTokenType.EndObject)
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/ConstantsMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/ConstantsMacroTypeConverter.cs
index 2d5bd7c..f998d46 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/ConstantsMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/ConstantsMacroTypeConverter.cs
@@ -25,7 +25,7 @@ public override ConstantsMacroType Read(ref Utf8JsonReader reader, Type typeToCo
public static ConstantsMacroType ReadContents(ref Utf8JsonReader reader)
{
- Dictionary values = new();
+ Dictionary values = [];
while (reader.Read())
{
@@ -39,11 +39,7 @@ public static ConstantsMacroType ReadContents(ref Utf8JsonReader reader)
{
throw new JsonException();
}
- string propertyName = reader.GetString();
- if (propertyName is null)
- {
- throw new JsonException();
- }
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Read value
reader.Read();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/EnumMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/EnumMacroTypeConverter.cs
index 3b62ffc..6037ac8 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/EnumMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/EnumMacroTypeConverter.cs
@@ -25,8 +25,8 @@ public override EnumMacroType Read(ref Utf8JsonReader reader, Type typeToConvert
public static EnumMacroType ReadContents(ref Utf8JsonReader reader)
{
- string name = null;
- Dictionary values = null;
+ string? name = null;
+ Dictionary? values = null;
while (reader.Read())
{
@@ -44,7 +44,7 @@ public static EnumMacroType ReadContents(ref Utf8JsonReader reader)
{
throw new JsonException();
}
- string propertyName = reader.GetString();
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Read either name or values
switch (propertyName)
@@ -72,7 +72,7 @@ private static Dictionary ReadValues(ref Utf8JsonReader reader)
throw new JsonException();
}
- Dictionary values = new();
+ Dictionary values = [];
while (reader.Read())
{
@@ -86,11 +86,7 @@ private static Dictionary ReadValues(ref Utf8JsonReader reader)
{
throw new JsonException();
}
- string propertyName = reader.GetString();
- if (propertyName is null)
- {
- throw new JsonException();
- }
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Read value
reader.Read();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/GameSpecificRegistryConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/GameSpecificRegistryConverter.cs
index f9b7f91..1c33302 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/GameSpecificRegistryConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/GameSpecificRegistryConverter.cs
@@ -10,14 +10,9 @@ This Source Code Form is subject to the terms of the Mozilla Public
namespace Underanalyzer.Decompiler.GameSpecific.Json;
-internal class GameSpecificRegistryConverter : JsonConverter
+internal class GameSpecificRegistryConverter(GameSpecificRegistry existing) : JsonConverter
{
- public GameSpecificRegistry Registry { get; }
-
- public GameSpecificRegistryConverter(GameSpecificRegistry existing)
- {
- Registry = existing;
- }
+ public GameSpecificRegistry Registry { get; } = existing;
public override GameSpecificRegistry Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
@@ -38,7 +33,7 @@ public override GameSpecificRegistry Read(ref Utf8JsonReader reader, Type typeTo
{
throw new JsonException();
}
- string propertyName = reader.GetString();
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Depending on property name, deserialize that component
switch (propertyName)
@@ -57,7 +52,7 @@ public override GameSpecificRegistry Read(ref Utf8JsonReader reader, Type typeTo
break;
case "NamedArguments":
reader.Read();
- NamedArgumentResolverConverter.ReadContents(ref reader, options, Registry.NamedArgumentResolver);
+ NamedArgumentResolverConverter.ReadContents(ref reader, Registry.NamedArgumentResolver);
break;
default:
throw new JsonException($"Unknown field {propertyName}");
@@ -86,7 +81,7 @@ private void ReadTypes(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
throw new JsonException();
}
- string propertyName = reader.GetString();
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Depending on property name, deserialize that component
switch (propertyName)
@@ -140,15 +135,11 @@ private void ReadMacroTypeList(ref Utf8JsonReader reader, JsonSerializerOptio
{
throw new JsonException();
}
- string propertyName = reader.GetString();
- if (propertyName is null)
- {
- throw new JsonException();
- }
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Deserialize macro type
reader.Read();
- T macroType = converter.Read(ref reader, typeof(T), options);
+ T macroType = converter.Read(ref reader, typeof(T), options) ?? throw new JsonException();
// Register macro type under name
Registry.RegisterType(propertyName, macroType);
@@ -178,15 +169,11 @@ private void ReadGeneralTypes(ref Utf8JsonReader reader, JsonSerializerOptions o
{
throw new JsonException();
}
- string propertyName = reader.GetString();
- if (propertyName is null)
- {
- throw new JsonException();
- }
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Deserialize macro type
reader.Read();
- IMacroType macroType = converter.Read(ref reader, typeof(IMacroType), options);
+ IMacroType macroType = converter.Read(ref reader, typeof(IMacroType), options) ?? throw new JsonException();
// Register macro type under name
Registry.RegisterType(propertyName, macroType);
@@ -213,11 +200,7 @@ private void ReadCodeEntryNames(ref Utf8JsonReader reader, JsonSerializerOptions
{
throw new JsonException();
}
- string propertyName = reader.GetString();
- if (propertyName is null)
- {
- throw new JsonException();
- }
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Read contents and register under code entry name
reader.Read();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/IMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/IMacroTypeConverter.cs
index ff885a3..16e3af0 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/IMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/IMacroTypeConverter.cs
@@ -11,16 +11,11 @@ This Source Code Form is subject to the terms of the Mozilla Public
namespace Underanalyzer.Decompiler.GameSpecific.Json;
-internal class IMacroTypeConverter : JsonConverter
+internal class IMacroTypeConverter(GameSpecificRegistry registry) : JsonConverter
{
- public GameSpecificRegistry Registry { get; }
+ public GameSpecificRegistry Registry { get; } = registry;
- public IMacroTypeConverter(GameSpecificRegistry registry)
- {
- Registry = registry;
- }
-
- public override IMacroType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ public override IMacroType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
@@ -31,13 +26,13 @@ public override IMacroType Read(ref Utf8JsonReader reader, Type typeToConvert, J
if (reader.TokenType == JsonTokenType.String)
{
// Read type name - access registry
- return Registry.FindType(reader.GetString());
+ return Registry.FindType(reader.GetString() ?? throw new JsonException());
}
if (reader.TokenType == JsonTokenType.StartArray)
{
// Read array of macro types as function arguments macro type
- List subMacroTypes = new();
+ List subMacroTypes = [];
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
@@ -45,7 +40,7 @@ public override IMacroType Read(ref Utf8JsonReader reader, Type typeToConvert, J
return new FunctionArgsMacroType(subMacroTypes);
}
- subMacroTypes.Add(Read(ref reader, typeToConvert, options));
+ subMacroTypes.Add(Read(ref reader, typeToConvert, options) ?? throw new JsonException());
}
throw new JsonException();
@@ -59,8 +54,8 @@ public override IMacroType Read(ref Utf8JsonReader reader, Type typeToConvert, J
{
throw new JsonException();
}
- string propertyName = reader.GetString();
- if (propertyName != "MacroType")
+ string? propertyName = reader.GetString();
+ if (propertyName is not "MacroType")
{
throw new JsonException();
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/IntersectMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/IntersectMacroTypeConverter.cs
index dc52e60..af47cec 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/IntersectMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/IntersectMacroTypeConverter.cs
@@ -29,7 +29,7 @@ public static IntersectMacroType ReadContents(ref Utf8JsonReader reader, IMacroT
throw new JsonException();
}
- List types = new();
+ List types = [];
while (reader.Read())
{
@@ -44,7 +44,7 @@ public static IntersectMacroType ReadContents(ref Utf8JsonReader reader, IMacroT
return new IntersectMacroType(types);
}
- types.Add(macroTypeConverter.Read(ref reader, null, options));
+ types.Add(macroTypeConverter.Read(ref reader, typeof(IMacroType), options) ?? throw new JsonException());
}
throw new JsonException();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/MatchMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/MatchMacroTypeConverter.cs
index 3b419a8..adcd8a0 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/MatchMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/MatchMacroTypeConverter.cs
@@ -12,9 +12,9 @@ internal class MatchMacroTypeConverter
{
public static MatchMacroType ReadContents(ref Utf8JsonReader reader, IMacroTypeConverter macroTypeConverter, JsonSerializerOptions options)
{
- IMacroType innerType = null;
- string conditionalValue = null;
- string conditionalType = null;
+ IMacroType? innerType = null;
+ string? conditionalValue = null;
+ string? conditionalType = null;
while (reader.Read())
{
@@ -24,7 +24,7 @@ public static MatchMacroType ReadContents(ref Utf8JsonReader reader, IMacroTypeC
{
throw new JsonException();
}
- return new MatchMacroType(innerType, conditionalType, conditionalValue);
+ return new MatchMacroType(innerType ?? throw new JsonException(), conditionalType, conditionalValue);
}
if (reader.TokenType != JsonTokenType.PropertyName)
@@ -57,7 +57,7 @@ public static MatchMacroType ReadContents(ref Utf8JsonReader reader, IMacroTypeC
{
throw new JsonException();
}
- innerType = macroTypeConverter.Read(ref reader, null, options);
+ innerType = macroTypeConverter.Read(ref reader, typeof(IMacroType), options);
break;
default:
throw new JsonException();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/NameMacroTypeResolverConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/NameMacroTypeResolverConverter.cs
index f276f34..39e8c31 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/NameMacroTypeResolverConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/NameMacroTypeResolverConverter.cs
@@ -30,7 +30,7 @@ public static void ReadContents(ref Utf8JsonReader reader, JsonSerializerOptions
{
throw new JsonException();
}
- string propertyName = reader.GetString();
+ string propertyName = reader.GetString() ?? throw new JsonException();
switch (propertyName)
{
@@ -74,11 +74,11 @@ private static void ReadMacroNameList(ref Utf8JsonReader reader, JsonSerializerO
{
throw new JsonException();
}
- string propertyName = reader.GetString();
+ string propertyName = reader.GetString() ?? throw new JsonException();
// Read and define macro type
reader.Read();
- define(propertyName, converter.Read(ref reader, typeof(IMacroType), options));
+ define(propertyName, converter.Read(ref reader, typeof(IMacroType), options) ?? throw new JsonException());
}
throw new JsonException();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/NamedArgumentResolverConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/NamedArgumentResolverConverter.cs
index 798dd08..ac9610f 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/NamedArgumentResolverConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/NamedArgumentResolverConverter.cs
@@ -11,7 +11,7 @@ namespace Underanalyzer.Decompiler.GameSpecific.Json;
internal class NamedArgumentResolverConverter
{
- public static void ReadContents(ref Utf8JsonReader reader, JsonSerializerOptions options, NamedArgumentResolver existing)
+ public static void ReadContents(ref Utf8JsonReader reader, NamedArgumentResolver existing)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
@@ -29,10 +29,10 @@ public static void ReadContents(ref Utf8JsonReader reader, JsonSerializerOptions
{
throw new JsonException();
}
- string codeEntryName = reader.GetString();
+ string codeEntryName = reader.GetString() ?? throw new JsonException();
// Read name array
- List names = new();
+ List names = [];
if (reader.TokenType != JsonTokenType.StartArray)
{
throw new JsonException();
@@ -48,7 +48,7 @@ public static void ReadContents(ref Utf8JsonReader reader, JsonSerializerOptions
{
throw new JsonException();
}
- names.Add(reader.GetString());
+ names.Add(reader.GetString() ?? throw new JsonException());
}
// Define the code entry with these names
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/NotMatchMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/NotMatchMacroTypeConverter.cs
index 0d25063..2d75a0c 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/NotMatchMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/NotMatchMacroTypeConverter.cs
@@ -12,9 +12,9 @@ internal class MatchNotMacroTypeConverter
{
public static MatchNotMacroType ReadContents(ref Utf8JsonReader reader, IMacroTypeConverter macroTypeConverter, JsonSerializerOptions options)
{
- IMacroType innerType = null;
- string conditionalValue = null;
- string conditionalType = null;
+ IMacroType? innerType = null;
+ string? conditionalValue = null;
+ string? conditionalType = null;
while (reader.Read())
{
@@ -24,7 +24,7 @@ public static MatchNotMacroType ReadContents(ref Utf8JsonReader reader, IMacroTy
{
throw new JsonException();
}
- return new MatchNotMacroType(innerType, conditionalType, conditionalValue);
+ return new MatchNotMacroType(innerType ?? throw new JsonException(), conditionalType, conditionalValue);
}
if (reader.TokenType != JsonTokenType.PropertyName)
@@ -60,7 +60,7 @@ public static MatchNotMacroType ReadContents(ref Utf8JsonReader reader, IMacroTy
{
throw new JsonException();
}
- innerType = macroTypeConverter.Read(ref reader, null, options);
+ innerType = macroTypeConverter.Read(ref reader, typeof(IMacroType), options);
break;
default:
throw new JsonException();
diff --git a/Underanalyzer/Decompiler/GameSpecific/Json/UnionMacroTypeConverter.cs b/Underanalyzer/Decompiler/GameSpecific/Json/UnionMacroTypeConverter.cs
index 194d76b..14b4ca4 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Json/UnionMacroTypeConverter.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Json/UnionMacroTypeConverter.cs
@@ -29,7 +29,7 @@ public static UnionMacroType ReadContents(ref Utf8JsonReader reader, IMacroTypeC
throw new JsonException();
}
- List types = new();
+ List types = [];
while (reader.Read())
{
@@ -44,7 +44,7 @@ public static UnionMacroType ReadContents(ref Utf8JsonReader reader, IMacroTypeC
return new UnionMacroType(types);
}
- types.Add(macroTypeConverter.Read(ref reader, null, options));
+ types.Add(macroTypeConverter.Read(ref reader, typeof(IMacroType), options) ?? throw new JsonException());
}
throw new JsonException();
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ArrayInitMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ArrayInitMacroType.cs
index 0193273..97d3ae8 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ArrayInitMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ArrayInitMacroType.cs
@@ -11,19 +11,14 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
/// Macro type that matches a macro type to an inline array initialization.
///
-public class ArrayInitMacroType : IMacroTypeArrayInit
+public class ArrayInitMacroType(IMacroType type) : IMacroTypeArrayInit
{
- public IMacroType Type { get; }
-
- public ArrayInitMacroType(IMacroType type)
- {
- Type = type;
- }
+ public IMacroType Type { get; } = type;
///
/// Resolves this macro type for a given array initialization in the AST.
///
- public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode array)
+ public ArrayInitNode? Resolve(ASTCleaner cleaner, ArrayInitNode array)
{
bool didAnything = false;
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/AssetMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/AssetMacroType.cs
index 1c2e354..83a9bc3 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/AssetMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/AssetMacroType.cs
@@ -11,16 +11,11 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
/// Macro type for GameMaker asset references.
///
-public class AssetMacroType : IMacroTypeInt32
+public class AssetMacroType(AssetType type) : IMacroTypeInt32
{
- public AssetType Type { get; }
+ public AssetType Type { get; } = type;
- public AssetMacroType(AssetType type)
- {
- Type = type;
- }
-
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
// Ensure we don't resolve this on newer GameMaker versions where this is unnecessary
if (cleaner.Context.GameContext.UsingAssetReferences)
@@ -32,7 +27,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, in
}
// Check for asset name with the given type
- string assetName = cleaner.Context.GameContext.GetAssetName(Type, data);
+ string? assetName = cleaner.Context.GameContext.GetAssetName(Type, data);
if (assetName is not null)
{
return new MacroValueNode(assetName);
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/BooleanMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/BooleanMacroType.cs
index 226c8a0..00cd5ac 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/BooleanMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/BooleanMacroType.cs
@@ -13,7 +13,7 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
public class BooleanMacroType : IMacroTypeInt32
{
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
// Ensure we don't resolve this on newer GameMaker versions where this is unnecessary
if (cleaner.Context.GameContext.UsingTypedBooleans)
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ColorMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ColorMacroType.cs
index c01421d..f6468b3 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ColorMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ColorMacroType.cs
@@ -41,7 +41,7 @@ public class ColorMacroType : IMacroTypeInt32
public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
// Check if in constant list
- if (Constants.TryGetValue(data, out string name))
+ if (Constants.TryGetValue(data, out string? name))
{
return new MacroValueNode(name);
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConditionalMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConditionalMacroType.cs
index 5bfa46e..9f04c6c 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConditionalMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConditionalMacroType.cs
@@ -11,37 +11,31 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
/// Base abstract type for all macro types that evaluate/verify a condition before passing through to another macro type.
///
-public abstract class ConditionalMacroType : IMacroTypeInt32, IMacroTypeInt64, IMacroTypeFunctionArgs, IMacroTypeArrayInit, IMacroTypeConditional
+///
+/// Inner type can be , specifying that the node will be passed through when resolved.
+///
+public abstract class ConditionalMacroType(IMacroType? innerType)
+ : IMacroTypeInt32, IMacroTypeInt64, IMacroTypeFunctionArgs, IMacroTypeArrayInit, IMacroTypeConditional
{
///
/// The inner type that this conditional macro type holds, which will be used after verifying the condition.
- /// If null, then there is no inner type, and the node being resolved will be passed through.
+ /// If , then there is no inner type, and the node being resolved will be passed through.
///
- public IMacroType InnerType { get; }
+ public IMacroType? InnerType { get; } = innerType;
// We make this macro type required when we aren't trying to satisfy for any inner type
public bool Required { get => InnerType is null; }
- ///
- /// Base constructor for all conditional macro types; requires inner type.
- /// Inner type can be null, specifying that the node will be passed through when resolved.
- ///
- public ConditionalMacroType(IMacroType innerType)
- {
- InnerType = innerType;
- }
-
///
/// Evaluates the condition on the given node, returning true if successful, or false if not.
///
public abstract bool EvaluateCondition(ASTCleaner cleaner, IConditionalValueNode node);
-
///
- /// Resolves the macro type with an arbitrary conditional node, passing through the node if successful; null otherwise.
- /// Inner type must be null for this to resolve anything.
+ /// Resolves the macro type with an arbitrary conditional node, passing through the node if successful; otherwise.
+ /// Inner type must be for this to resolve anything.
///
- public IExpressionNode Resolve(ASTCleaner cleaner, IConditionalValueNode node)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IConditionalValueNode node)
{
if (InnerType is not null)
{
@@ -59,7 +53,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IConditionalValueNode node)
return node;
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
if (node is not IConditionalValueNode conditionalValueNode)
{
@@ -97,7 +91,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, in
}
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
{
if (node is not IConditionalValueNode conditionalValueNode)
{
@@ -136,7 +130,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, lo
}
}
- public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode call)
+ public IFunctionCallNode? Resolve(ASTCleaner cleaner, IFunctionCallNode call)
{
// Check whether we specify any inner type at all
if (InnerType is not null)
@@ -169,7 +163,7 @@ public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode call)
}
}
- public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
+ public ArrayInitNode? Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
{
// Check whether we specify any inner type at all
if (InnerType is not null)
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConstantsMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConstantsMacroType.cs
index d964325..1560687 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConstantsMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/ConstantsMacroType.cs
@@ -43,15 +43,17 @@ public ConstantsMacroType(Dictionary constants)
///
public ConstantsMacroType(Type enumType)
{
- foreach (int value in Enum.GetValues(enumType))
+ Array values = Enum.GetValues(enumType);
+ ValueToConstantName = new(values.Length);
+ foreach (int value in values)
{
- ValueToConstantName[value] = Enum.GetName(enumType, value);
+ ValueToConstantName[value] = Enum.GetName(enumType, value) ?? throw new NullReferenceException();
}
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
- if (ValueToConstantName.TryGetValue(data, out string name))
+ if (ValueToConstantName.TryGetValue(data, out string? name))
{
return new MacroValueNode(name);
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/EnumMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/EnumMacroType.cs
index a4ebfb3..590d906 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/EnumMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/EnumMacroType.cs
@@ -44,18 +44,20 @@ public EnumMacroType(string name, Dictionary values)
/// Constructs a macro type from an enum, where value names are the constant names,
/// associated with their enum values.
///
- public EnumMacroType(Type enumType, string name = null)
+ public EnumMacroType(Type enumType, string? name = null)
{
Name = name ?? enumType.Name;
- foreach (long value in Enum.GetValues(enumType))
+ Array values = Enum.GetValues(enumType);
+ ValueToValueName = new(values.Length);
+ foreach (long value in values)
{
- ValueToValueName[value] = Enum.GetName(enumType, value);
+ ValueToValueName[value] = Enum.GetName(enumType, value) ?? throw new NullReferenceException();
}
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
{
- if (ValueToValueName.TryGetValue(data, out string valueName))
+ if (ValueToValueName.TryGetValue(data, out string? valueName))
{
// Use enum node, and declare new enum if one doesn't exist already
if (!cleaner.Context.NameToEnumDeclaration.ContainsKey(Name))
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/FunctionArgsMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/FunctionArgsMacroType.cs
index 13cea43..f792471 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/FunctionArgsMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/FunctionArgsMacroType.cs
@@ -12,19 +12,14 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
/// Macro type that matches an array of macro types to a function call.
///
-public class FunctionArgsMacroType : IMacroType, IMacroTypeFunctionArgs
+public class FunctionArgsMacroType(IEnumerable types) : IMacroType, IMacroTypeFunctionArgs
{
- private List Types { get; }
-
- public FunctionArgsMacroType(IEnumerable types)
- {
- Types = new(types);
- }
+ private List Types { get; } = new(types);
///
/// Resolves this macro type for a given function call in the AST.
///
- public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode call)
+ public IFunctionCallNode? Resolve(ASTCleaner cleaner, IFunctionCallNode call)
{
int callArgumentsCount = call.Arguments.Count;
int callArgumentsStart = 0;
@@ -45,14 +40,15 @@ public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode call)
List resolved = new(callArgumentsCount);
for (int i = callArgumentsStart; i < (callArgumentsStart + callArgumentsCount); i++)
{
- if (Types[i - callArgumentsStart] is null || call.Arguments[i] is not IMacroResolvableNode node)
+ IMacroType? currentType = Types[i - callArgumentsStart];
+ if (currentType is null || call.Arguments[i] is not IMacroResolvableNode node)
{
// Current type is not defined, or current argument is not resolvable, so just use existing argument
resolved.Add(call.Arguments[i]);
continue;
}
- if (node.ResolveMacroType(cleaner, Types[i - callArgumentsStart]) is not IExpressionNode nodeResolved)
+ if (node.ResolveMacroType(cleaner, currentType) is not IExpressionNode nodeResolved)
{
// Failed to resolve current argument's macro type.
// If the type is a conditional which is required in this scope, then fail this resolution;
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/InstanceMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/InstanceMacroType.cs
index b48cc05..a139c4d 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/InstanceMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/InstanceMacroType.cs
@@ -13,7 +13,7 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
public class InstanceMacroType : IMacroTypeInt32
{
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
return data switch
{
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/IntersectMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/IntersectMacroType.cs
index e3f8b01..b9f2cd9 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/IntersectMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/IntersectMacroType.cs
@@ -34,9 +34,9 @@ public IntersectMacroType(IEnumerable types)
}
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
- IExpressionNode result = null;
+ IExpressionNode? result = null;
foreach (IMacroType type in Types)
{
if (type is not IMacroTypeInt32 type32)
@@ -52,9 +52,9 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, in
return result;
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
{
- IExpressionNode result = null;
+ IExpressionNode? result = null;
foreach (IMacroType type in Types)
{
if (type is not IMacroTypeInt64 type64)
@@ -70,9 +70,9 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, lo
return result;
}
- public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode functionCall)
+ public IFunctionCallNode? Resolve(ASTCleaner cleaner, IFunctionCallNode functionCall)
{
- IFunctionCallNode result = null;
+ IFunctionCallNode? result = null;
foreach (IMacroType type in Types)
{
if (type is not IMacroTypeFunctionArgs typeFuncArgs)
@@ -88,9 +88,9 @@ public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode functionC
return result;
}
- public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
+ public ArrayInitNode? Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
{
- ArrayInitNode result = null;
+ ArrayInitNode? result = null;
foreach (IMacroType type in Types)
{
if (type is not IMacroTypeArrayInit typeArrayInit)
@@ -106,9 +106,9 @@ public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
return result;
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IConditionalValueNode node)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IConditionalValueNode node)
{
- IExpressionNode result = null;
+ IExpressionNode? result = null;
foreach (IMacroType type in Types)
{
if (type is not IMacroTypeConditional typeConditional)
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchMacroType.cs
index 5f3d96e..eebf3de 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchMacroType.cs
@@ -11,23 +11,17 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
/// Conditional for matching an AST node by type name and/or by value.
///
-public class MatchMacroType : ConditionalMacroType
+public class MatchMacroType(IMacroType? innerType, string? typeName, string? value = null) : ConditionalMacroType(innerType)
{
///
- /// Type name to match.
+ /// Type name to match, or if none.
///
- public string ConditionalTypeName { get; }
+ public string? ConditionalTypeName { get; } = typeName;
///
- /// Value content to match, or null if none.
+ /// Value content to match, or if none.
///
- public string ConditionalValue { get; }
-
- public MatchMacroType(IMacroType innerType, string typeName, string value = null) : base(innerType)
- {
- ConditionalTypeName = typeName;
- ConditionalValue = value;
- }
+ public string? ConditionalValue { get; } = value;
public override bool EvaluateCondition(ASTCleaner cleaner, IConditionalValueNode node)
{
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchNotMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchNotMacroType.cs
index b354635..9285c8e 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchNotMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/MatchNotMacroType.cs
@@ -11,23 +11,17 @@ namespace Underanalyzer.Decompiler.GameSpecific;
///
/// Conditional for matching an AST node by *not* having a type name and/or value.
///
-public class MatchNotMacroType : ConditionalMacroType
+public class MatchNotMacroType(IMacroType? innerType, string? typeName, string? value = null) : ConditionalMacroType(innerType)
{
///
- /// Type name to *not* match.
+ /// Type name to *not* match, or if none.
///
- public string ConditionalTypeName { get; }
+ public string? ConditionalTypeName { get; } = typeName;
///
- /// Value content to *not* match, or null if none.
+ /// Value content to *not* match, or if none.
///
- public string ConditionalValue { get; }
-
- public MatchNotMacroType(IMacroType innerType, string typeName, string value = null) : base(innerType)
- {
- ConditionalTypeName = typeName;
- ConditionalValue = value;
- }
+ public string? ConditionalValue { get; } = value;
public override bool EvaluateCondition(ASTCleaner cleaner, IConditionalValueNode node)
{
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/UnionMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/UnionMacroType.cs
index 9f8f339..3cd54a8 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/UnionMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/UnionMacroType.cs
@@ -34,7 +34,7 @@ public UnionMacroType(IEnumerable types)
}
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
foreach (IMacroType type in Types)
{
@@ -42,7 +42,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, in
{
continue;
}
- IExpressionNode result = type32.Resolve(cleaner, node, data);
+ IExpressionNode? result = type32.Resolve(cleaner, node, data);
if (result is not null)
{
return result;
@@ -51,7 +51,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, in
return null;
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, long data)
{
foreach (IMacroType type in Types)
{
@@ -59,7 +59,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, lo
{
continue;
}
- IExpressionNode result = type64.Resolve(cleaner, node, data);
+ IExpressionNode? result = type64.Resolve(cleaner, node, data);
if (result is not null)
{
return result;
@@ -68,7 +68,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, lo
return null;
}
- public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode functionCall)
+ public IFunctionCallNode? Resolve(ASTCleaner cleaner, IFunctionCallNode functionCall)
{
foreach (IMacroType type in Types)
{
@@ -76,7 +76,7 @@ public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode functionC
{
continue;
}
- IFunctionCallNode result = typeFuncArgs.Resolve(cleaner, functionCall);
+ IFunctionCallNode? result = typeFuncArgs.Resolve(cleaner, functionCall);
if (result is not null)
{
return result;
@@ -85,7 +85,7 @@ public IFunctionCallNode Resolve(ASTCleaner cleaner, IFunctionCallNode functionC
return null;
}
- public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
+ public ArrayInitNode? Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
{
foreach (IMacroType type in Types)
{
@@ -93,7 +93,7 @@ public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
{
continue;
}
- ArrayInitNode result = typeArrayInit.Resolve(cleaner, arrayInit);
+ ArrayInitNode? result = typeArrayInit.Resolve(cleaner, arrayInit);
if (result is not null)
{
return result;
@@ -102,7 +102,7 @@ public ArrayInitNode Resolve(ASTCleaner cleaner, ArrayInitNode arrayInit)
return null;
}
- public IExpressionNode Resolve(ASTCleaner cleaner, IConditionalValueNode node)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IConditionalValueNode node)
{
foreach (IMacroType type in Types)
{
@@ -110,7 +110,7 @@ public IExpressionNode Resolve(ASTCleaner cleaner, IConditionalValueNode node)
{
continue;
}
- IExpressionNode result = typeConditional.Resolve(cleaner, node);
+ IExpressionNode? result = typeConditional.Resolve(cleaner, node);
if (result is not null)
{
return result;
diff --git a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/VirtualKeyMacroType.cs b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/VirtualKeyMacroType.cs
index 0c337fc..ea1eadb 100644
--- a/Underanalyzer/Decompiler/GameSpecific/MacroTypes/VirtualKeyMacroType.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/MacroTypes/VirtualKeyMacroType.cs
@@ -73,10 +73,10 @@ public class VirtualKeyMacroType : IMacroTypeInt32
{ 165, "vk_ralt" }
};
- public IExpressionNode Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
+ public IExpressionNode? Resolve(ASTCleaner cleaner, IMacroResolvableNode node, int data)
{
// Check if in constant list
- if (Constants.TryGetValue(data, out string name))
+ if (Constants.TryGetValue(data, out string? name))
{
return new MacroValueNode(name);
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/Resolvers/GlobalMacroTypeResolver.cs b/Underanalyzer/Decompiler/GameSpecific/Resolvers/GlobalMacroTypeResolver.cs
index b0b3d30..7519782 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Resolvers/GlobalMacroTypeResolver.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Resolvers/GlobalMacroTypeResolver.cs
@@ -24,7 +24,7 @@ public class GlobalMacroTypeResolver : IMacroTypeResolver
public GlobalMacroTypeResolver()
{
GlobalNames = new NameMacroTypeResolver();
- CodeEntryNames = new();
+ CodeEntryNames = [];
}
///
@@ -35,16 +35,16 @@ public void DefineCodeEntry(string codeEntry, NameMacroTypeResolver resolver)
CodeEntryNames[codeEntry] = resolver;
}
- public IMacroType ResolveVariableType(ASTCleaner cleaner, string variableName)
+ public IMacroType? ResolveVariableType(ASTCleaner cleaner, string? variableName)
{
if (variableName is null)
{
return null;
}
- if (CodeEntryNames.TryGetValue(cleaner.TopFragmentContext.CodeEntryName, out NameMacroTypeResolver resolver))
+ if (CodeEntryNames.TryGetValue(cleaner.TopFragmentContext!.CodeEntryName!, out NameMacroTypeResolver? resolver))
{
- IMacroType resolved = resolver.ResolveVariableType(cleaner, variableName);
+ IMacroType? resolved = resolver.ResolveVariableType(cleaner, variableName);
if (resolved is not null)
{
return resolved;
@@ -54,16 +54,16 @@ public IMacroType ResolveVariableType(ASTCleaner cleaner, string variableName)
return GlobalNames.ResolveVariableType(cleaner, variableName);
}
- public IMacroType ResolveFunctionArgumentTypes(ASTCleaner cleaner, string functionName)
+ public IMacroType? ResolveFunctionArgumentTypes(ASTCleaner cleaner, string? functionName)
{
if (functionName is null)
{
return null;
}
- if (CodeEntryNames.TryGetValue(cleaner.TopFragmentContext.CodeEntryName, out NameMacroTypeResolver resolver))
+ if (CodeEntryNames.TryGetValue(cleaner.TopFragmentContext!.CodeEntryName!, out NameMacroTypeResolver? resolver))
{
- IMacroType resolved = resolver.ResolveFunctionArgumentTypes(cleaner, functionName);
+ IMacroType? resolved = resolver.ResolveFunctionArgumentTypes(cleaner, functionName);
if (resolved is not null)
{
return resolved;
@@ -73,16 +73,16 @@ public IMacroType ResolveFunctionArgumentTypes(ASTCleaner cleaner, string functi
return GlobalNames.ResolveFunctionArgumentTypes(cleaner, functionName);
}
- public IMacroType ResolveReturnValueType(ASTCleaner cleaner, string functionName)
+ public IMacroType? ResolveReturnValueType(ASTCleaner cleaner, string? functionName)
{
if (functionName is null)
{
return null;
}
- if (CodeEntryNames.TryGetValue(cleaner.TopFragmentContext.CodeEntryName, out NameMacroTypeResolver resolver))
+ if (CodeEntryNames.TryGetValue(cleaner.TopFragmentContext!.CodeEntryName!, out NameMacroTypeResolver? resolver))
{
- IMacroType resolved = resolver.ResolveReturnValueType(cleaner, functionName);
+ IMacroType? resolved = resolver.ResolveReturnValueType(cleaner, functionName);
if (resolved is not null)
{
return resolved;
diff --git a/Underanalyzer/Decompiler/GameSpecific/Resolvers/NameMacroTypeResolver.cs b/Underanalyzer/Decompiler/GameSpecific/Resolvers/NameMacroTypeResolver.cs
index 67a1d57..af75b2a 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Resolvers/NameMacroTypeResolver.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Resolvers/NameMacroTypeResolver.cs
@@ -23,9 +23,9 @@ public class NameMacroTypeResolver : IMacroTypeResolver
///
public NameMacroTypeResolver()
{
- Variables = new();
- FunctionArguments = new();
- FunctionReturn = new();
+ Variables = [];
+ FunctionArguments = [];
+ FunctionReturn = [];
}
///
@@ -64,27 +64,27 @@ public void DefineFunctionReturnType(string name, IMacroType type)
FunctionReturn[name] = type;
}
- public IMacroType ResolveVariableType(ASTCleaner cleaner, string variableName)
+ public IMacroType? ResolveVariableType(ASTCleaner cleaner, string? variableName)
{
- if (variableName is not null && Variables.TryGetValue(variableName, out IMacroType macroType))
+ if (variableName is not null && Variables.TryGetValue(variableName, out IMacroType? macroType))
{
return macroType;
}
return null;
}
- public IMacroType ResolveFunctionArgumentTypes(ASTCleaner cleaner, string functionName)
+ public IMacroType? ResolveFunctionArgumentTypes(ASTCleaner cleaner, string? functionName)
{
- if (functionName is not null && FunctionArguments.TryGetValue(functionName, out IMacroType macroType))
+ if (functionName is not null && FunctionArguments.TryGetValue(functionName, out IMacroType? macroType))
{
return macroType;
}
return null;
}
- public IMacroType ResolveReturnValueType(ASTCleaner cleaner, string functionName)
+ public IMacroType? ResolveReturnValueType(ASTCleaner cleaner, string? functionName)
{
- if (functionName is not null && FunctionReturn.TryGetValue(functionName, out IMacroType macroType))
+ if (functionName is not null && FunctionReturn.TryGetValue(functionName, out IMacroType? macroType))
{
return macroType;
}
diff --git a/Underanalyzer/Decompiler/GameSpecific/Resolvers/NamedArgumentResolver.cs b/Underanalyzer/Decompiler/GameSpecific/Resolvers/NamedArgumentResolver.cs
index 4ddb6d5..4d8e768 100644
--- a/Underanalyzer/Decompiler/GameSpecific/Resolvers/NamedArgumentResolver.cs
+++ b/Underanalyzer/Decompiler/GameSpecific/Resolvers/NamedArgumentResolver.cs
@@ -17,7 +17,7 @@ public class NamedArgumentResolver
///
public NamedArgumentResolver()
{
- FunctionArgumentNames = new();
+ FunctionArgumentNames = [];
}
///
@@ -30,11 +30,11 @@ public void DefineCodeEntry(string codeEntry, IEnumerable names)
///
/// Resolves a named argument for a given code entry, with the given argument index.
- /// Returns the name, or null if none is defined.
+ /// Returns the name, or if none is defined.
///
- public string ResolveArgument(string codeEntryName, int argumentIndex)
+ public string? ResolveArgument(string codeEntryName, int argumentIndex)
{
- if (FunctionArgumentNames.TryGetValue(codeEntryName, out List names))
+ if (FunctionArgumentNames.TryGetValue(codeEntryName, out List? names))
{
if (argumentIndex >= 0 && argumentIndex < names.Count)
{
diff --git a/Underanalyzer/Decompiler/GlobalFunctions.cs b/Underanalyzer/Decompiler/GlobalFunctions.cs
index c308f60..e86611d 100644
--- a/Underanalyzer/Decompiler/GlobalFunctions.cs
+++ b/Underanalyzer/Decompiler/GlobalFunctions.cs
@@ -5,6 +5,7 @@ This Source Code Form is subject to the terms of the Mozilla Public
*/
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Underanalyzer.Decompiler.ControlFlow;
using static Underanalyzer.IGMInstruction;
@@ -42,8 +43,8 @@ public class GlobalFunctions : IGlobalFunctions
///
public GlobalFunctions()
{
- FunctionToName = new();
- NameToFunction = new();
+ FunctionToName = [];
+ NameToFunction = [];
}
///
@@ -51,10 +52,10 @@ public GlobalFunctions()
/// Optionally, can be passed in to configure parallelization.
/// By default, the default settings are used (which has no limits).
///
- public GlobalFunctions(IEnumerable globalScripts, ParallelOptions parallelOptions = null)
+ public GlobalFunctions(IEnumerable globalScripts, ParallelOptions? parallelOptions = null)
{
- Dictionary functionToName = new();
- Dictionary nameToFunction = new();
+ Dictionary functionToName = [];
+ Dictionary nameToFunction = [];
object _lock = new();
Parallel.ForEach(globalScripts, parallelOptions ?? new(), script =>
@@ -72,15 +73,13 @@ public GlobalFunctions(IEnumerable globalScripts, ParallelOptions paral
// If no successors, assume code is corrupt and don't consider it
continue;
}
- Block after = fragment.Successors[0] as Block;
- if (after is null)
+ if (fragment.Successors[0] is not Block after)
{
// If block after isn't a block, assume code is corrupt as well
continue;
}
- string name = GetFunctionNameAfterFragment(after, out IGMFunction function);
- if (name is not null)
+ if (GetFunctionNameAfterFragment(after, out string? name, out IGMFunction? function))
{
lock (_lock)
{
@@ -97,29 +96,30 @@ public GlobalFunctions(IEnumerable globalScripts, ParallelOptions paral
///
/// Gets the name of a global function based on the instructions after a code fragment.
- /// Returns null if there is none, or the code is corrupt.
+ /// Returns true if one is found, or false if the code is corrupt or one could not be found.
///
- private string GetFunctionNameAfterFragment(Block block, out IGMFunction foundFunction)
+ private static bool GetFunctionNameAfterFragment(Block block, [MaybeNullWhen(false)] out string name, [MaybeNullWhen(false)] out IGMFunction foundFunction)
{
+ name = null;
foundFunction = null;
// Ensure enough instructions exist
if (block.Instructions.Count < 3)
{
- return null;
+ return false;
}
// Get function reference for fragment
if (block.Instructions[0] is not { Kind: Opcode.Push, Type1: DataType.Int32, Function: IGMFunction function } || function is null)
{
- return null;
+ return false;
}
foundFunction = function;
// Ensure conv instruction exists
if (block.Instructions[1] is not { Kind: Opcode.Convert, Type1: DataType.Int32, Type2: DataType.Variable })
{
- return null;
+ return false;
}
switch (block.Instructions[2].Kind)
@@ -137,7 +137,7 @@ private string GetFunctionNameAfterFragment(Block block, out IGMFunction foundFu
])
{
// Failed to match instructions
- return null;
+ return false;
}
// Check if we have a name
@@ -151,7 +151,8 @@ private string GetFunctionNameAfterFragment(Block block, out IGMFunction foundFu
])
{
// We have a name!
- return funcName;
+ name = funcName;
+ return true;
}
break;
}
@@ -167,7 +168,7 @@ private string GetFunctionNameAfterFragment(Block block, out IGMFunction foundFu
])
{
// Failed to match instructions
- return null;
+ return false;
}
// Check if we're a struct or function constructor (named)
@@ -184,13 +185,14 @@ private string GetFunctionNameAfterFragment(Block block, out IGMFunction foundFu
if (pushVal != -16 && pushVal != -5)
{
// We're a constructor!
- return funcName;
+ name = funcName;
+ return true;
}
}
break;
}
}
- return null;
+ return false;
}
}
diff --git a/Underanalyzer/IGameContext.cs b/Underanalyzer/IGameContext.cs
index def6dee..4f22f6e 100644
--- a/Underanalyzer/IGameContext.cs
+++ b/Underanalyzer/IGameContext.cs
@@ -91,5 +91,5 @@ public interface IGameContext
///
/// Returns the string name of an asset, or if no such asset exists.
///
- public string GetAssetName(AssetType assetType, int assetIndex);
+ public string? GetAssetName(AssetType assetType, int assetIndex);
}
diff --git a/Underanalyzer/Mock/GameContextMock.cs b/Underanalyzer/Mock/GameContextMock.cs
index d2361f8..fb72956 100644
--- a/Underanalyzer/Mock/GameContextMock.cs
+++ b/Underanalyzer/Mock/GameContextMock.cs
@@ -37,7 +37,7 @@ public class GameContextMock : IGameContext
///
/// A Dictionary that mocks asset chunks and their contents.
///
- public Dictionary> MockAssets { get; set; } = new();
+ public Dictionary> MockAssets { get; set; } = [];
///
/// Define a new mock asset that gets added to .
@@ -47,10 +47,9 @@ public class GameContextMock : IGameContext
/// The name of the asset.
public void DefineMockAsset(AssetType assetType, int assetIndex, string assetName)
{
- Dictionary assets;
- if (!MockAssets.TryGetValue(assetType, out assets))
+ if (!MockAssets.TryGetValue(assetType, out Dictionary? assets))
{
- assets = new();
+ assets = [];
MockAssets.Add(assetType, assets);
}
assets[assetIndex] = assetName;
@@ -62,11 +61,14 @@ public void DefineMockAsset(AssetType assetType, int assetIndex, string assetNam
/// The asset type of the asset that should be fetched.
/// The index of the asset that should be fetched.
/// The name of the asset, or if it does not exist.
- public string GetMockAsset(AssetType assetType, int assetIndex)
+ public string? GetMockAsset(AssetType assetType, int assetIndex)
{
- if (!MockAssets.TryGetValue(assetType, out var dict)) return null;
+ if (!MockAssets.TryGetValue(assetType, out var dict))
+ {
+ return null;
+ }
- if (dict.TryGetValue(assetIndex, out string name))
+ if (dict.TryGetValue(assetIndex, out string? name))
{
return name;
}
@@ -74,7 +76,7 @@ public string GetMockAsset(AssetType assetType, int assetIndex)
}
///
- public string GetAssetName(AssetType assetType, int assetIndex)
+ public string? GetAssetName(AssetType assetType, int assetIndex)
{
return assetType switch
{
diff --git a/Underanalyzer/Mock/VMAssemblyMock.cs b/Underanalyzer/Mock/VMAssemblyMock.cs
index f8f3ec2..5fd3bb4 100644
--- a/Underanalyzer/Mock/VMAssemblyMock.cs
+++ b/Underanalyzer/Mock/VMAssemblyMock.cs
@@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the Mozilla Public
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -17,10 +18,10 @@ namespace Underanalyzer.Mock;
///
public static class VMAssembly
{
- private static readonly Dictionary StringToOpcode = new();
- private static readonly Dictionary StringToExtOpcode = new();
- private static readonly Dictionary CharToDataType = new();
- private static readonly Dictionary StringToAssetType = new();
+ private static readonly Dictionary StringToOpcode = [];
+ private static readonly Dictionary StringToExtOpcode = [];
+ private static readonly Dictionary CharToDataType = [];
+ private static readonly Dictionary StringToAssetType = [];
///
/// Initializes precomputed data for parsing VM assembly
@@ -31,8 +32,8 @@ static VMAssembly()
Type typeOpcode = typeof(IGMInstruction.Opcode);
foreach (IGMInstruction.Opcode opcode in Enum.GetValues(typeOpcode))
{
- var field = typeOpcode.GetField(Enum.GetName(typeOpcode, opcode));
- var info = field.GetCustomAttribute();
+ var field = typeOpcode.GetField(Enum.GetName(typeOpcode, opcode)!)!;
+ var info = field.GetCustomAttribute()!;
StringToOpcode[info.Mnemonic] = opcode;
}
@@ -40,8 +41,8 @@ static VMAssembly()
Type typeExtType = typeof(IGMInstruction.ExtendedOpcode);
foreach (IGMInstruction.ExtendedOpcode opcode in Enum.GetValues(typeExtType))
{
- var field = typeExtType.GetField(Enum.GetName(typeExtType, opcode));
- var info = field.GetCustomAttribute();
+ var field = typeExtType.GetField(Enum.GetName(typeExtType, opcode)!)!;
+ var info = field.GetCustomAttribute()!;
StringToOpcode[info.Mnemonic] = IGMInstruction.Opcode.Extended;
StringToExtOpcode[info.Mnemonic] = opcode;
}
@@ -50,8 +51,8 @@ static VMAssembly()
Type typeDataType = typeof(IGMInstruction.DataType);
foreach (IGMInstruction.DataType dataType in Enum.GetValues(typeDataType))
{
- var field = typeDataType.GetField(Enum.GetName(typeDataType, dataType));
- var info = field.GetCustomAttribute();
+ var field = typeDataType.GetField(Enum.GetName(typeDataType, dataType)!)!;
+ var info = field.GetCustomAttribute()!;
CharToDataType[info.Mnemonic] = dataType;
}
@@ -59,20 +60,20 @@ static VMAssembly()
Type typeAssetType = typeof(AssetType);
foreach (AssetType assetType in Enum.GetValues(typeAssetType))
{
- StringToAssetType[Enum.GetName(typeAssetType, assetType)] = assetType;
+ StringToAssetType[Enum.GetName(typeAssetType, assetType)!] = assetType;
}
}
///
/// Parses VM assembly into mock instruction data.
///
- public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameContext context, string name = "root")
+ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameContext? context, string name = "root")
{
- List instructions = new();
+ List instructions = [];
GMCode root = new(name, instructions);
- Dictionary branchLabelAddresses = new();
- List<(string Label, GMInstruction Instr)> branchTargets = new();
+ Dictionary branchLabelAddresses = [];
+ List<(string Label, GMInstruction Instr)> branchTargets = [];
HashSet variables = new(new GMVariableComparer());
HashSet functions = new(new GMFunctionComparer());
@@ -89,18 +90,24 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
{
// Totally empty line; ignore
if (line.Length == 0)
+ {
continue;
+ }
// Ignore comment lines
if (line[0] == '#')
+ {
continue;
+ }
// Branch label
if (line[0] == ':')
{
string label = line[1..].Trim();
if (label.Length < 3 || label[0] != '[' || label[^1] != ']')
+ {
throw new Exception("Invalid branch header");
+ }
branchLabelAddresses[label[1..^1]] = address;
continue;
}
@@ -132,9 +139,13 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
{
// If we're at the start and are the same name as the root, then just perform an update
if (localCount is not null)
+ {
root.LocalCount = localCount.Value;
+ }
if (argCount is not null)
+ {
root.ArgumentCount = argCount.Value;
+ }
continue;
}
@@ -153,16 +164,19 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
string[] parts = line.Trim().Split(' ');
// Empty line; ignore
- if (parts.Length == 0 || string.IsNullOrEmpty(parts[0]))
+ if (parts.Length == 0 || string.IsNullOrEmpty(parts[0]))
+ {
continue;
+ }
string[] opcodeParts = parts[0].Split('.');
// Parse opcode
string opcodeStr = opcodeParts[0].ToLowerInvariant();
- IGMInstruction.Opcode opcode;
- if (!StringToOpcode.TryGetValue(opcodeStr, out opcode))
+ if (!StringToOpcode.TryGetValue(opcodeStr, out IGMInstruction.Opcode opcode))
+ {
throw new Exception($"Unexpected opcode \"{opcodeStr}\"");
+ }
// Parse data types
IGMInstruction.DataType type1 = 0;
@@ -170,28 +184,40 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
if (opcodeParts.Length >= 2)
{
if (opcodeParts[1].Length != 1)
+ {
throw new Exception("Expected single character for data type 1");
+ }
char c = opcodeParts[1].ToLowerInvariant()[0];
if (!CharToDataType.TryGetValue(c, out type1))
+ {
throw new Exception($"Unexpected data type \"{c}\"");
+ }
}
if (opcodeParts.Length >= 3)
{
if (opcodeParts[2].Length != 1)
+ {
throw new Exception("Expected single character for data type 2");
+ }
char c = opcodeParts[2].ToLowerInvariant()[0];
if (!CharToDataType.TryGetValue(c, out type2))
+ {
throw new Exception($"Unexpected data type \"{c}\"");
+ }
}
if (opcodeParts.Length >= 4)
+ {
throw new Exception("Too many data types");
+ }
// Construct mock instruction
- GMInstruction instr = new();
- instr.Address = address;
- instr.Kind = opcode;
- instr.Type1 = type1;
- instr.Type2 = type2;
+ GMInstruction instr = new()
+ {
+ Address = address,
+ Kind = opcode,
+ Type1 = type1,
+ Type2 = type2
+ };
// Parse additional data
switch (opcode)
@@ -200,7 +226,9 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
{
// Parse comparison kind
if (parts.Length < 2)
+ {
throw new Exception("Compare needs comparison kind parameter");
+ }
instr.ComparisonKind = parts[1].ToLowerInvariant() switch
{
"lt" => IGMInstruction.ComparisonType.LesserThan,
@@ -216,20 +244,26 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
case IGMInstruction.Opcode.Pop:
{
if (parts.Length < 2)
+ {
throw new Exception("Pop needs parameter");
+ }
// Parse swap variant
if (instr.Type1 == IGMInstruction.DataType.Int16)
{
if (!byte.TryParse(parts[1], out byte popSwapSize) || popSwapSize < 5 || popSwapSize > 6)
+ {
throw new Exception("Unexpected pop swap size");
+ }
instr.PopSwapSize = popSwapSize;
break;
}
// Parse variable destination
if (!ParseVariableFromString(parts[1], variables, out var variable, out var varType, out var instType))
+ {
throw new Exception($"Failed to parse variable {parts[1]}");
+ }
instr.Variable = variable;
instr.ReferenceVarType = varType;
instr.InstType = instType;
@@ -239,16 +273,22 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
{
// Parse normal dup size
if (parts.Length < 2)
+ {
throw new Exception("Duplicate needs size parameter");
+ }
if (!byte.TryParse(parts[1], out byte dupSize))
+ {
throw new Exception("Failed to parse parameter");
+ }
instr.DuplicationSize = dupSize;
// Parse "swap" mode size, if parameter exists
if (parts.Length >= 3)
{
if (!byte.TryParse(parts[2], out byte dupSize2))
+ {
throw new Exception("Failed to parse parameter");
+ }
instr.DuplicationSize2 = dupSize2;
}
}
@@ -257,9 +297,13 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
{
// Parse argument count
if (parts.Length < 2)
+ {
throw new Exception("CallVariable needs size parameter");
+ }
if (!int.TryParse(parts[1], out int argCount))
+ {
throw new Exception("Failed to parse parameter");
+ }
instr.ArgumentCount = argCount;
}
break;
@@ -270,7 +314,9 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
case IGMInstruction.Opcode.PopWithContext:
{
if (parts.Length < 2)
+ {
throw new Exception("Branch instruction needs target");
+ }
string target = parts[1];
// PopWithContext has an exception to this branch target
@@ -282,7 +328,9 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
// Parse normal target
if (target.Length < 3 || target[0] != '[' || target[^1] != ']')
+ {
throw new Exception("Invalid branch target format");
+ }
// Store this target for later
branchTargets.Add((target[1..^1], instr));
@@ -295,14 +343,18 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
case IGMInstruction.Opcode.PushImmediate:
{
if (parts.Length < 2)
+ {
throw new Exception("Push instruction needs data");
+ }
string data = parts[1];
switch (type1)
{
case IGMInstruction.DataType.Double:
if (!double.TryParse(data, out double dataDouble))
+ {
throw new Exception("Invalid double");
+ }
instr.ValueDouble = dataDouble;
break;
case IGMInstruction.DataType.Int32:
@@ -312,7 +364,7 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
{
// We're pushing a function index instead
GMFunction function = new(data["[function]".Length..]);
- if (functions.TryGetValue(function, out GMFunction existingFunction))
+ if (functions.TryGetValue(function, out GMFunction? existingFunction))
{
// We found a function that was already created
instr.Function = existingFunction;
@@ -326,7 +378,7 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
if (data.StartsWith("[variable]"))
{
// We're pushing a variable hash instead
- instr.Variable = new GMVariable() { Name = new GMString(data["[variable]".Length..]) };
+ instr.Variable = new GMVariable(new GMString(data["[variable]".Length..]));
break;
}
throw new Exception("Unknown push.i value");
@@ -335,17 +387,23 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
break;
case IGMInstruction.DataType.Int64:
if (!long.TryParse(data, out long dataInt64))
+ {
throw new Exception("Invalid int64");
+ }
instr.ValueLong = dataInt64;
break;
case IGMInstruction.DataType.Boolean:
if (!bool.TryParse(data, out bool dataBool))
+ {
throw new Exception("Invalid boolean");
+ }
instr.ValueBool = dataBool;
break;
case IGMInstruction.DataType.Variable:
if (!ParseVariableFromString(data, variables, out var variable, out var varType, out var instType))
+ {
throw new Exception($"Failed to parse variable {parts[1]}");
+ }
instr.Variable = variable;
instr.ReferenceVarType = varType;
instr.InstType = instType;
@@ -356,7 +414,9 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
break;
case IGMInstruction.DataType.Int16:
if (!short.TryParse(data, out short dataInt16))
+ {
throw new Exception("Invalid int16");
+ }
instr.ValueShort = dataInt16;
break;
}
@@ -365,10 +425,12 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
case IGMInstruction.Opcode.Call:
{
if (parts.Length < 3)
+ {
throw new Exception("Call needs function and argument count");
+ }
GMFunction function = new(parts[1]);
- if (functions.TryGetValue(function, out GMFunction existingFunction))
+ if (functions.TryGetValue(function, out GMFunction? existingFunction))
{
// We found a function that was already created
instr.Function = existingFunction;
@@ -381,23 +443,28 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
}
if (!int.TryParse(parts[2], out int argCount))
+ {
throw new Exception("Failed to parse argument count");
+ }
instr.ArgumentCount = argCount;
}
break;
case IGMInstruction.Opcode.Extended:
{
// Parse extended opcode type
- IGMInstruction.ExtendedOpcode extOpcode;
- if (!StringToExtOpcode.TryGetValue(opcodeStr, out extOpcode))
+ if (!StringToExtOpcode.TryGetValue(opcodeStr, out IGMInstruction.ExtendedOpcode extOpcode))
+ {
throw new Exception($"Unexpected extended opcode \"{opcodeStr}\"");
+ }
instr.ExtKind = extOpcode;
// Parse additional arguments
if (extOpcode == IGMInstruction.ExtendedOpcode.PushReference)
{
if (parts.Length < 2)
+ {
throw new Exception("PushReference needs reference ID (or function)");
+ }
if (!int.TryParse(parts[1], out int referenceID))
{
// Not a reference ID. Instead, a function reference
@@ -405,7 +472,9 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
break;
}
if (parts.Length < 3)
+ {
throw new Exception("PushReference needs reference ID and type parameters");
+ }
instr.AssetReferenceId = referenceID;
instr.AssetReferenceType = StringToAssetType[parts[2]];
}
@@ -419,42 +488,50 @@ public static GMCode ParseAssemblyFromLines(IEnumerable lines, IGameCont
}
// Resolve branch targets
- foreach (var target in branchTargets)
+ foreach ((string targetLabel, GMInstruction targetInstr) in branchTargets)
{
// Look up label
- if (!branchLabelAddresses.TryGetValue(target.Label, out int labelAddress))
- throw new Exception($"Did not find matching label for \"{target.Label}\"");
+ if (!branchLabelAddresses.TryGetValue(targetLabel, out int labelAddress))
+ {
+ throw new Exception($"Did not find matching label for \"{targetLabel}\"");
+ }
- target.Instr.BranchOffset = labelAddress - target.Instr.Address;
+ targetInstr.BranchOffset = labelAddress - targetInstr.Address;
}
// Update code lengths
root.Length = address;
foreach (var child in root.Children)
+ {
child.Length = address;
+ }
return root;
}
private static bool ParseVariableFromString(
- string str, HashSet variables, out GMVariable variable,
+ string str, HashSet variables, [MaybeNullWhen(false)] out GMVariable variable,
out IGMInstruction.VariableType varType, out IGMInstruction.InstanceType instType)
{
// Default data
varType = IGMInstruction.VariableType.Normal;
instType = IGMInstruction.InstanceType.Self;
- variable = new();
+ variable = null;
// If too small, exit early
if (str.Length < 2)
+ {
return false;
+ }
// Parse variable type
if (str[0] == '[')
{
int closingBracket = str.IndexOf(']');
if (closingBracket == -1)
+ {
return false;
+ }
string varTypeStr = str[1..closingBracket].ToLowerInvariant();
varType = varTypeStr switch
@@ -475,7 +552,9 @@ private static bool ParseVariableFromString(
// Parse instance type
int dot = str.IndexOf('.');
if (dot == -1)
+ {
return false;
+ }
string instTypeStr = str[..dot];
if (short.TryParse(instTypeStr, out short instTypeObjectId))
{
@@ -502,12 +581,14 @@ private static bool ParseVariableFromString(
str = str[(dot + 1)..];
// Update variable
- variable.Name = new GMString(str);
- variable.InstanceType = instType; // TODO: does this match actual game behavior?
- variable.VariableID = 0; // TODO: do we want to make actual IDs? probably not?
+ variable = new GMVariable(new GMString(str))
+ {
+ InstanceType = instType, // TODO: does this match actual game behavior?
+ VariableID = 0 // TODO: do we want to make actual IDs? probably not?
+ };
// Check existing variables - if this already exists, return existing variable
- if (variables.TryGetValue(variable, out GMVariable existingVariable))
+ if (variables.TryGetValue(variable, out GMVariable? existingVariable))
{
variable = existingVariable;
}
@@ -553,7 +634,9 @@ private static string UnescapeStringContents(string str)
}
if (escapeActive)
+ {
throw new Exception("Invalid escape at end of string");
+ }
return sb.ToString();
}
@@ -568,11 +651,15 @@ private static string UnescapeStringContents(string str)
while (digitEnd < str.Length)
{
if (!char.IsDigit(str[digitEnd]))
+ {
break;
+ }
digitEnd++;
}
if (int.TryParse(str[digitStart..digitEnd], out int paramValue))
+ {
return paramValue;
+ }
}
return null;
}
diff --git a/Underanalyzer/Mock/VMDataMock.cs b/Underanalyzer/Mock/VMDataMock.cs
index 1f96126..4ac2252 100644
--- a/Underanalyzer/Mock/VMDataMock.cs
+++ b/Underanalyzer/Mock/VMDataMock.cs
@@ -12,30 +12,32 @@ namespace Underanalyzer.Mock;
///
/// A default implementation of the interface.
///
-public class GMCode : IGMCode
+/// The name of the code entry.
+/// List of instructions contained within the code entry.
+public class GMCode(string name, List instructions) : IGMCode
{
///
/// The name of the code entry.
///
- public GMString Name { get; set; }
-
+ public GMString Name { get; set; } = new(name);
+
///
public int Length { get; set; } = 0;
-
+
///
/// A list of instructions this entry has.
///
- public List Instructions { get; set; }
-
+ public List Instructions { get; set; } = instructions;
+
///
/// The parent code entry.
///
- public GMCode Parent { get; set; } = null;
-
+ public GMCode? Parent { get; set; } = null;
+
///
/// A list of child code entries.
///
- public List Children { get; set; } = new();
+ public List Children { get; set; } = [];
///
public int StartOffset { get; set; } = 0;
@@ -46,19 +48,8 @@ public class GMCode : IGMCode
///
public int LocalCount { get; set; } = 0;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the code entry.
- /// A list of instructions.
- public GMCode(string name, List instructions)
- {
- Name = new(name);
- Instructions = instructions;
- }
-
// Interface implementation
-
+
///
IGMString IGMCode.Name => Name;
@@ -66,7 +57,7 @@ public GMCode(string name, List instructions)
public int InstructionCount => Instructions.Count;
///
- IGMCode IGMCode.Parent => Parent;
+ IGMCode? IGMCode.Parent => Parent;
///
public int ChildCount => Children.Count;
@@ -112,10 +103,10 @@ public class GMInstruction : IGMInstruction
public IGMInstruction.InstanceType InstType { get; set; }
///
- public IGMVariable Variable { get; set; }
+ public IGMVariable? Variable { get; set; }
///
- public IGMFunction Function { get; set; }
+ public IGMFunction? Function { get; set; }
///
public IGMInstruction.VariableType ReferenceVarType { get; set; }
@@ -136,7 +127,7 @@ public class GMInstruction : IGMInstruction
public bool ValueBool { get; set; }
///
- public IGMString ValueString { get; set; }
+ public IGMString? ValueString { get; set; }
///
public int BranchOffset { get => ValueInt; set => ValueInt = value; }
@@ -175,16 +166,11 @@ public override string ToString()
///
/// A default implementation of the interface.
///
-public class GMString : IGMString
+/// The content contained within the string.
+public class GMString(string content) : IGMString
{
///
- public string Content { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The content of a string.
- public GMString(string content) => Content = content;
+ public string Content { get; set; } = content;
///
public override string ToString()
@@ -196,10 +182,10 @@ public override string ToString()
///
/// A default implementation of the interface.
///
-public class GMVariable : IGMVariable
+public class GMVariable(IGMString name) : IGMVariable
{
///
- public IGMString Name { get; set; }
+ public IGMString Name { get; set; } = name;
///
public IGMInstruction.InstanceType InstanceType { get; set; }
@@ -220,8 +206,12 @@ public override string ToString()
public class GMVariableComparer : IEqualityComparer
{
///
- public bool Equals(GMVariable x, GMVariable y)
+ public bool Equals(GMVariable? x, GMVariable? y)
{
+ if (x is null || y is null)
+ {
+ throw new NullReferenceException();
+ }
return x.Name.Content == y.Name.Content && x.InstanceType == y.InstanceType && x.VariableID == y.VariableID;
}
@@ -235,16 +225,11 @@ public int GetHashCode(GMVariable obj)
///
/// A default implementation of the interface.
///
-public class GMFunction : IGMFunction
+/// The name of the function.
+public class GMFunction(string name) : IGMFunction
{
///
- public IGMString Name { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The function name.
- public GMFunction(string name) => Name = new GMString(name);
+ public IGMString Name { get; set; } = new GMString(name);
///
public override string ToString()
@@ -252,7 +237,7 @@ public override string ToString()
return $"{nameof(GMFunction)}: {Name.Content}";
}
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
if (obj is not GMFunction func)
{
@@ -273,8 +258,12 @@ public override int GetHashCode()
public class GMFunctionComparer : IEqualityComparer
{
///
- public bool Equals(GMFunction x, GMFunction y)
+ public bool Equals(GMFunction? x, GMFunction? y)
{
+ if (x is null || y is null)
+ {
+ throw new NullReferenceException();
+ }
return x.Name.Content == y.Name.Content;
}
diff --git a/Underanalyzer/Underanalyzer.csproj b/Underanalyzer/Underanalyzer.csproj
index 003581e..ceb6418 100644
--- a/Underanalyzer/Underanalyzer.csproj
+++ b/Underanalyzer/Underanalyzer.csproj
@@ -3,6 +3,7 @@
netstandard2.1;net6.0;net7.0;net8.0
12
+ enable
diff --git a/Underanalyzer/VMData.cs b/Underanalyzer/VMData.cs
index 717dbdf..cf75dc1 100644
--- a/Underanalyzer/VMData.cs
+++ b/Underanalyzer/VMData.cs
@@ -41,7 +41,7 @@ public interface IGMCode
///
/// Parent code entry, if this is a sub-function entry. Otherwise, if a root code entry, this is .
///
- public IGMCode Parent { get; }
+ public IGMCode? Parent { get; }
///
/// Gets a child code entry at the specified index.
@@ -72,48 +72,33 @@ public interface IGMInstruction
///
/// Mnemonic attribute used for opcodes.
///
- public class OpcodeInfo : Attribute
+ /// A unique shorthand identifier.
+ [AttributeUsage(AttributeTargets.Field)]
+ public class OpcodeInfo(string mnemonic) : Attribute
{
///
/// Unique shorthand identifier used for this opcode.
///
- public string Mnemonic { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// A unique shorthand identifier.
- public OpcodeInfo(string mnemonic)
- {
- Mnemonic = mnemonic;
- }
+ public string Mnemonic { get; } = mnemonic;
}
///
/// Mnemonic attribute used for VM data types.
///
- public class DataTypeInfo : Attribute
+ /// A unique character to represent this type.
+ /// How many bytes the type takes on the VM stack.
+ [AttributeUsage(AttributeTargets.Field)]
+ public class DataTypeInfo(char mnemonic, int size) : Attribute
{
///
/// Unique character used to represent this data type.
///
- public char Mnemonic { get; }
+ public char Mnemonic { get; } = mnemonic;
///
/// Size in bytes taken on the VM stack.
///
- public int Size { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// A unique character to represent this type.
- /// How many bytes the type takes on the VM stack.
- public DataTypeInfo(char mnemonic, int size)
- {
- Mnemonic = mnemonic;
- Size = size;
- }
+ public int Size { get; } = size;
}
///
@@ -653,12 +638,12 @@ public enum VariableType : byte
///
/// For instructions that reference a variable, represents the variable being referenced.
///
- public IGMVariable Variable { get; }
+ public IGMVariable? Variable { get; }
///
/// For instructions that reference a function, represents the function being referenced.
///
- public IGMFunction Function { get; }
+ public IGMFunction? Function { get; }
///
/// For instructions that reference a variable or function, this represents the variable type.
@@ -693,7 +678,7 @@ public enum VariableType : byte
///
/// Represents a string value for instructions that push strings.
///
- public IGMString ValueString { get; }
+ public IGMString? ValueString { get; }
///
/// Represents a branch offset for branch instructions, in bytes.
@@ -729,13 +714,13 @@ public enum VariableType : byte
///
/// For instructions with opcode,
- /// this is the ID of the asset supplied with the instruction, if is null.
+ /// this is the ID of the asset supplied with the instruction, if is .
///
public int AssetReferenceId { get; }
///
/// For instructions with opcode,
- /// this returns the type of the asset supplied with the instruction, if is null.
+ /// this returns the type of the asset supplied with the instruction, if is .
///
public AssetType GetAssetReferenceType(IGameContext context);
@@ -745,7 +730,9 @@ public enum VariableType : byte
internal static int GetSize(IGMInstruction instr)
{
if (instr.Variable is not null || instr.Function is not null)
+ {
return 8;
+ }
switch (instr.Kind)
{
case Opcode.Push or
@@ -754,14 +741,20 @@ Opcode.PushGlobal or
Opcode.PushBuiltin or
Opcode.PushImmediate:
if (instr.Type1 is (DataType.Double or DataType.Int64))
+ {
return 12;
+ }
if (instr.Type1 != DataType.Int16)
+ {
return 8;
+ }
break;
case Opcode.Extended:
if (instr.Type1 == DataType.Int32)
+ {
return 8;
+ }
break;
}
return 4;
diff --git a/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Loops.cs b/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Loops.cs
index 4b81e54..752ace5 100644
--- a/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Loops.cs
+++ b/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Loops.cs
@@ -72,7 +72,7 @@ b [0]
Assert.Equal(blocks[3], b.Else);
Assert.Empty(blocks[3].Successors);
Assert.Empty(b.True.Predecessors);
- Assert.Empty(b.Else.Predecessors);
+ Assert.Empty(b.Else!.Predecessors);
Assert.Equal(2, blocks[2].Instructions.Count);
@@ -419,7 +419,7 @@ b [0]
c = (ContinueNode)blocks[4].Successors[0];
Assert.Equal([blocks[4]], c.Predecessors);
Assert.Equal([], c.Successors);
- Assert.Empty(b.Else.Predecessors);
+ Assert.Empty(b.Else!.Predecessors);
Assert.Empty(b.True.Predecessors);
Assert.Empty(blocks[3].Instructions);
@@ -509,7 +509,7 @@ b [0]
c = (ContinueNode)blocks[4].Successors[0];
Assert.Equal([blocks[4]], c.Predecessors);
Assert.Equal([], c.Successors);
- Assert.Empty(b.Else.Predecessors);
+ Assert.Empty(b.Else!.Predecessors);
Assert.Empty(b.True.Predecessors);
Assert.Empty(blocks[3].Instructions);
@@ -730,7 +730,7 @@ b [0]
c = (BreakNode)blocks[4].Successors[0];
Assert.Equal([blocks[4]], c.Predecessors);
Assert.Equal([], c.Successors);
- Assert.Empty(b.Else.Predecessors);
+ Assert.Empty(b.Else!.Predecessors);
Assert.Empty(b.True.Predecessors);
Assert.Empty(blocks[3].Instructions);
@@ -819,7 +819,7 @@ b [0]
c = (BreakNode)blocks[4].Successors[0];
Assert.Equal([blocks[4]], c.Predecessors);
Assert.Equal([], c.Successors);
- Assert.Empty(b.Else.Predecessors);
+ Assert.Empty(b.Else!.Predecessors);
Assert.Empty(b.True.Predecessors);
Assert.Empty(blocks[3].Instructions);
@@ -1286,7 +1286,7 @@ b [0]
Assert.Equal([blocks[5]], c.Predecessors);
Assert.Equal([], c.Successors);
Assert.Empty(b1.True.Predecessors);
- Assert.Empty(b1.Else.Predecessors);
+ Assert.Empty(b1.Else!.Predecessors);
TestUtil.VerifyFlowDirections(blocks);
TestUtil.VerifyFlowDirections(fragments);
@@ -1433,7 +1433,7 @@ b [0]
Assert.Equal(blocks[4], b1.Else);
Assert.Equal(blocks[4], b1.False);
Assert.Empty(b1.True.Predecessors);
- Assert.Empty(b1.Else.Predecessors);
+ Assert.Empty(b1.Else!.Predecessors);
Assert.Empty(blocks[3].Instructions);
Assert.Empty(blocks[4].Instructions);
diff --git a/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Switch.cs b/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Switch.cs
index ff57825..21a7159 100644
--- a/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Switch.cs
+++ b/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.Switch.cs
@@ -46,8 +46,8 @@ b [3]
Assert.Single(blocks[2].Successors);
Assert.IsType(blocks[2].Successors[0]);
Assert.Equal([blocks[3]], blocks[2].Successors[0].Successors); // specifically, break goes to the following block, always
- Assert.Contains(blocks[3], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[3], ctx.SwitchContinueBlocks);
+ Assert.Contains(blocks[3], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[3], ctx.SwitchContinueBlocks!);
TestUtil.VerifyFlowDirections(blocks);
TestUtil.VerifyFlowDirections(fragments);
@@ -105,8 +105,8 @@ b [5]
Assert.IsType(blocks[i].Successors[0]);
Assert.Equal([blocks[i + 1]], blocks[i].Successors[0].Successors);
}
- Assert.Contains(blocks[5], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[5], ctx.SwitchContinueBlocks);
+ Assert.Contains(blocks[5], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[5], ctx.SwitchContinueBlocks!);
TestUtil.VerifyFlowDirections(blocks);
TestUtil.VerifyFlowDirections(fragments);
@@ -166,12 +166,12 @@ b [0]
Assert.Null(loop.ForLoopIncrementor);
- Assert.Contains(blocks[6], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[5], ctx.SwitchEndNodes);
- Assert.Contains(blocks[5], ctx.SwitchContinueBlocks);
- Assert.DoesNotContain(blocks[6], ctx.SwitchContinueBlocks);
- Assert.Contains(blocks[4], ctx.SwitchIgnoreJumpBlocks);
- Assert.DoesNotContain(blocks[5], ctx.SwitchIgnoreJumpBlocks);
+ Assert.Contains(blocks[6], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[5], ctx.SwitchEndNodes!);
+ Assert.Contains(blocks[5], ctx.SwitchContinueBlocks!);
+ Assert.DoesNotContain(blocks[6], ctx.SwitchContinueBlocks!);
+ Assert.Contains(blocks[4], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.DoesNotContain(blocks[5], ctx.SwitchIgnoreJumpBlocks!);
Assert.Single(blocks[3].Successors);
Assert.IsType(blocks[3].Successors[0]);
Assert.Equal([blocks[4]], blocks[3].Successors[0].Successors);
@@ -246,13 +246,13 @@ b [0]
Assert.Null(loop.ForLoopIncrementor);
- Assert.Contains(blocks[8], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[7], ctx.SwitchEndNodes);
- Assert.Contains(blocks[7], ctx.SwitchContinueBlocks);
- Assert.DoesNotContain(blocks[8], ctx.SwitchContinueBlocks);
- Assert.Contains(blocks[3], ctx.SwitchIgnoreJumpBlocks);
- Assert.Contains(blocks[6], ctx.SwitchIgnoreJumpBlocks);
- Assert.DoesNotContain(blocks[7], ctx.SwitchIgnoreJumpBlocks);
+ Assert.Contains(blocks[8], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[7], ctx.SwitchEndNodes!);
+ Assert.Contains(blocks[7], ctx.SwitchContinueBlocks!);
+ Assert.DoesNotContain(blocks[8], ctx.SwitchContinueBlocks!);
+ Assert.Contains(blocks[3], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.Contains(blocks[6], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.DoesNotContain(blocks[7], ctx.SwitchIgnoreJumpBlocks!);
for (int i = 4; i <= 5; i++)
{
Assert.Single(blocks[i].Successors);
@@ -333,13 +333,13 @@ b [0]
Assert.Null(b.Else);
Assert.Empty(b.True.Predecessors);
- Assert.Contains(blocks[7], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[6], ctx.SwitchEndNodes);
- Assert.Contains(blocks[6], ctx.SwitchContinueBlocks);
- Assert.DoesNotContain(blocks[7], ctx.SwitchContinueBlocks);
- Assert.Contains(blocks[2], ctx.SwitchIgnoreJumpBlocks);
- Assert.Contains(blocks[5], ctx.SwitchIgnoreJumpBlocks);
- Assert.DoesNotContain(blocks[6], ctx.SwitchIgnoreJumpBlocks);
+ Assert.Contains(blocks[7], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[6], ctx.SwitchEndNodes!);
+ Assert.Contains(blocks[6], ctx.SwitchContinueBlocks!);
+ Assert.DoesNotContain(blocks[7], ctx.SwitchContinueBlocks!);
+ Assert.Contains(blocks[2], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.Contains(blocks[5], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.DoesNotContain(blocks[6], ctx.SwitchIgnoreJumpBlocks!);
Assert.Single(blocks[4].Successors);
Assert.IsType(blocks[4].Successors[0]);
Assert.Empty(blocks[4].Successors[0].Successors);
@@ -443,13 +443,13 @@ b [0]
Assert.Null(b1.Else);
Assert.Empty(b1.True.Predecessors);
- Assert.Contains(blocks[11], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[10], ctx.SwitchEndNodes);
- Assert.Contains(blocks[10], ctx.SwitchContinueBlocks);
- Assert.DoesNotContain(blocks[11], ctx.SwitchContinueBlocks);
- Assert.Contains(blocks[3], ctx.SwitchIgnoreJumpBlocks);
- Assert.Contains(blocks[9], ctx.SwitchIgnoreJumpBlocks);
- Assert.DoesNotContain(blocks[10], ctx.SwitchIgnoreJumpBlocks);
+ Assert.Contains(blocks[11], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[10], ctx.SwitchEndNodes!);
+ Assert.Contains(blocks[10], ctx.SwitchContinueBlocks!);
+ Assert.DoesNotContain(blocks[11], ctx.SwitchContinueBlocks!);
+ Assert.Contains(blocks[3], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.Contains(blocks[9], ctx.SwitchIgnoreJumpBlocks!);
+ Assert.DoesNotContain(blocks[10], ctx.SwitchIgnoreJumpBlocks!);
Assert.Single(blocks[6].Successors);
Assert.IsType(blocks[6].Successors[0]);
@@ -501,10 +501,10 @@ b [3]
Assert.Empty(loops);
Assert.Empty(branches);
- Assert.Contains(blocks[3], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[3], ctx.SwitchContinueBlocks);
- Assert.DoesNotContain(blocks[2], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[2], ctx.SwitchContinueBlocks);
+ Assert.Contains(blocks[3], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[3], ctx.SwitchContinueBlocks!);
+ Assert.DoesNotContain(blocks[2], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[2], ctx.SwitchContinueBlocks!);
TestUtil.VerifyFlowDirections(blocks);
TestUtil.VerifyFlowDirections(fragments);
@@ -565,10 +565,10 @@ b [5]
Assert.Empty(loops);
Assert.Empty(branches);
- Assert.Contains(blocks[5], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[5], ctx.SwitchContinueBlocks);
- Assert.Contains(blocks[6], ctx.SwitchEndNodes);
- Assert.DoesNotContain(blocks[6], ctx.SwitchContinueBlocks);
+ Assert.Contains(blocks[5], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[5], ctx.SwitchContinueBlocks!);
+ Assert.Contains(blocks[6], ctx.SwitchEndNodes!);
+ Assert.DoesNotContain(blocks[6], ctx.SwitchContinueBlocks!);
TestUtil.VerifyFlowDirections(blocks);
TestUtil.VerifyFlowDirections(fragments);
diff --git a/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.cs b/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.cs
index 3b673cc..3f438f9 100644
--- a/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.cs
+++ b/UnderanalyzerTest/BinaryBranch.FindBinaryBranches.cs
@@ -172,7 +172,7 @@ pop.v.i self.c
Assert.Equal(blocks[2], b.False);
Assert.Equal(blocks[2], b.Else);
Assert.Empty(b.True.Predecessors);
- Assert.Empty(b.Else.Predecessors);
+ Assert.Empty(b.Else!.Predecessors);
Assert.Equal(2, blocks[1].Instructions.Count);
@@ -414,7 +414,7 @@ pop.v.i self.g
Assert.Equal(b2, b0.False);
Assert.Equal(b2, b0.Else);
Assert.Empty(b0.True.Predecessors);
- Assert.Empty(b0.Else.Predecessors);
+ Assert.Empty(b0.Else!.Predecessors);
Assert.Equal([], b1.Predecessors);
Assert.Equal([blocks[4]], b1.Successors);
@@ -423,7 +423,7 @@ pop.v.i self.g
Assert.Equal(blocks[3], b1.False);
Assert.Equal(blocks[3], b1.Else);
Assert.Empty(b1.True.Predecessors);
- Assert.Empty(b1.Else.Predecessors);
+ Assert.Empty(b1.Else!.Predecessors);
Assert.Equal([], b2.Predecessors);
Assert.Equal([], b2.Successors);
@@ -432,7 +432,7 @@ pop.v.i self.g
Assert.Equal(blocks[7], b2.False);
Assert.Equal(blocks[7], b2.Else);
Assert.Empty(b2.True.Predecessors);
- Assert.Empty(b2.Else.Predecessors);
+ Assert.Empty(b2.Else!.Predecessors);
Assert.Equal(2, blocks[2].Instructions.Count);
Assert.Empty(blocks[4].Instructions);
@@ -554,7 +554,7 @@ b [6]
Assert.Equal(b2, b0.False);
Assert.Equal(b2, b0.Else);
Assert.Empty(b0.True.Predecessors);
- Assert.Empty(b0.Else.Predecessors);
+ Assert.Empty(b0.Else!.Predecessors);
Assert.Equal([], b1.Predecessors);
Assert.Equal([blocks[3]], b1.Successors);
@@ -564,7 +564,7 @@ b [6]
Assert.Equal(blocks[3], b1.False);
Assert.IsType(b1.Else);
Assert.Empty(b1.True.Predecessors);
- Assert.Empty(b1.Else.Predecessors);
+ Assert.Empty(b1.Else!.Predecessors);
Assert.Equal([], b2.Predecessors);
Assert.Equal([], b2.Successors);
@@ -573,7 +573,7 @@ b [6]
Assert.Equal(blocks[6], b2.False);
Assert.IsType(b2.Else);
Assert.Empty(b2.True.Predecessors);
- Assert.Empty(b2.Else.Predecessors);
+ Assert.Empty(b2.Else!.Predecessors);
Assert.Empty(blocks[2].Instructions);
Assert.Empty(blocks[3].Instructions);
@@ -626,7 +626,7 @@ bf [3]
Assert.Equal(b1, b0.False);
Assert.Equal(b1, b0.Else);
Assert.Empty(b0.True.Predecessors);
- Assert.Empty(b0.Else.Predecessors);
+ Assert.Empty(b0.Else!.Predecessors);
Assert.Equal([], b1.Predecessors);
Assert.Equal([], b1.Successors);
@@ -845,7 +845,7 @@ b [4]
Assert.Equal(blocks[3], b0.Else);
Assert.Equal(blocks[3], b0.False);
Assert.Empty(b0.True.Predecessors);
- Assert.Empty(b0.Else.Predecessors);
+ Assert.Empty(b0.Else!.Predecessors);
Assert.Empty(blocks[2].Instructions);
diff --git a/UnderanalyzerTest/Loop.FindLoops.cs b/UnderanalyzerTest/Loop.FindLoops.cs
index b04b23b..d74e86c 100644
--- a/UnderanalyzerTest/Loop.FindLoops.cs
+++ b/UnderanalyzerTest/Loop.FindLoops.cs
@@ -187,7 +187,7 @@ b [1]
Assert.IsType(loops[1]);
WhileLoop loop0 = (WhileLoop)loops[0];
WhileLoop loop1 = (WhileLoop)loops[1];
- Assert.Equal(loop0, fragments[0].Children[0].Successors[0]);
+ Assert.Equal(loop0, fragments[0].Children[0]!.Successors[0]);
Assert.Equal(loop1, loop0.Body);
Assert.Equal(loop0, loop1.Parent);
Assert.Equal(blocks[3], loop1.Body);
@@ -582,7 +582,7 @@ b [0]
Assert.Empty(loop1.Tail.Successors);
Assert.Empty(loop0.Head.Predecessors);
Assert.Empty(loop0.Tail.Successors);
- Assert.Empty(loop0.Body.Predecessors);
+ Assert.Empty(loop0.Body!.Predecessors);
TestUtil.VerifyFlowDirections(blocks);
TestUtil.VerifyFlowDirections(fragments);
@@ -636,7 +636,7 @@ bf [0]
Assert.IsType(loop1.After);
Assert.Empty(loop1.After.Successors);
Assert.Empty(loop1.Head.Predecessors);
- Assert.Empty(loop1.Body.Predecessors);
+ Assert.Empty(loop1.Body!.Predecessors);
Assert.Empty(loop1.Tail.Successors);
Assert.Empty(loop0.Head.Predecessors);
Assert.Empty(loop0.Tail.Successors);
@@ -959,7 +959,7 @@ b [6]
Assert.Equal(blocks[6], loop0.Successors[0]);
Assert.Empty(loop0.Head.Predecessors);
Assert.Empty(loop0.Tail.Successors);
- Assert.Empty(loop0.BreakBlock.Predecessors);
+ Assert.Empty(loop0.BreakBlock!.Predecessors);
Assert.Empty(loop0.BreakBlock.Successors);
Assert.Equal([loop0.After], blocks[2].Successors);
@@ -1022,7 +1022,7 @@ b [8]
Assert.Equal(blocks[8], loop0.Successors[0]);
Assert.Empty(loop0.Head.Predecessors);
Assert.Empty(loop0.Tail.Successors);
- Assert.Empty(loop0.BreakBlock.Predecessors);
+ Assert.Empty(loop0.BreakBlock!.Predecessors);
Assert.Empty(loop0.BreakBlock.Successors);
Assert.Equal([loop0.After], blocks[2].Successors);
Assert.Equal([loop0.Tail], blocks[4].Successors);
@@ -1147,14 +1147,14 @@ b [10]
Assert.IsType(loop0.After);
Assert.Empty(loop0.After.Successors);
Assert.Equal(blocks[9], loop0.BreakBlock);
- Assert.Empty(loop0.BreakBlock.Predecessors);
+ Assert.Empty(loop0.BreakBlock!.Predecessors);
Assert.Empty(loop0.BreakBlock.Successors);
Assert.Equal(blocks[2], loop1.Head);
Assert.Equal(blocks[3], loop1.Tail);
Assert.IsType(loop1.After);
Assert.Empty(loop1.After.Successors);
Assert.Equal(blocks[5], loop1.BreakBlock);
- Assert.Empty(loop1.BreakBlock.Predecessors);
+ Assert.Empty(loop1.BreakBlock!.Predecessors);
Assert.Empty(loop1.BreakBlock.Successors);
Assert.Equal([loop1.After], blocks[2].Successors);
Assert.Equal([loop0.After], blocks[6].Successors);
@@ -1228,8 +1228,6 @@ popenv [11]
Assert.IsType(loops[0]);
Assert.IsType(loops[1]);
Assert.IsType(loops[2]);
- WithLoop loop0 = (WithLoop)loops[0];
- WithLoop loop1 = (WithLoop)loops[1];
WithLoop loop2 = (WithLoop)loops[2];
Assert.Equal([loop2], blocks[10].Successors);
diff --git a/UnderanalyzerTest/TestUtil.cs b/UnderanalyzerTest/TestUtil.cs
index 7bcb0d7..9f623c2 100644
--- a/UnderanalyzerTest/TestUtil.cs
+++ b/UnderanalyzerTest/TestUtil.cs
@@ -51,8 +51,8 @@ public static void VerifyFlowDirections(IEnumerable nodes)
///
public static void EnsureNoRemainingJumps(DecompileContext ctx)
{
- List blocks = ctx.Blocks;
- List branches = ctx.BinaryBranchNodes;
+ List blocks = ctx.Blocks!;
+ List branches = ctx.BinaryBranchNodes!;
foreach (BinaryBranch bb in branches)
{
diff --git a/UnderanalyzerTest/TryCatch.FindTryCatch.cs b/UnderanalyzerTest/TryCatch.FindTryCatch.cs
index 3a37a90..87ad915 100644
--- a/UnderanalyzerTest/TryCatch.FindTryCatch.cs
+++ b/UnderanalyzerTest/TryCatch.FindTryCatch.cs
@@ -129,7 +129,7 @@ call.i @@try_unhook@@ 0
Assert.Single(blocks[1].Successors);
Assert.IsType(blocks[1].Successors[0]);
Assert.Equal(blocks[2], tc.Catch);
- Assert.Empty(tc.Catch.Predecessors);
+ Assert.Empty(tc.Catch!.Predecessors);
Assert.Single(blocks[2].Successors);
Assert.IsType(blocks[2].Successors[0]);
@@ -234,7 +234,7 @@ call.i @@try_unhook@@ 0
Assert.Equal(blocks[1], tc0.Try);
Assert.Equal(blocks[6], tc0.Catch);
Assert.Empty(tc0.Try.Predecessors);
- Assert.Empty(tc0.Catch.Predecessors);
+ Assert.Empty(tc0.Catch!.Predecessors);
Assert.Single(blocks[5].Successors);
Assert.IsType(blocks[5].Successors[0]);
Assert.Single(blocks[11].Successors);
@@ -246,7 +246,7 @@ call.i @@try_unhook@@ 0
Assert.Equal(blocks[2], tc1.Try);
Assert.Equal(blocks[3], tc1.Catch);
Assert.Empty(tc1.Try.Predecessors);
- Assert.Empty(tc1.Catch.Predecessors);
+ Assert.Empty(tc1.Catch!.Predecessors);
Assert.Single(blocks[2].Successors);
Assert.IsType(blocks[2].Successors[0]);
Assert.Single(blocks[3].Successors);
@@ -258,7 +258,7 @@ call.i @@try_unhook@@ 0
Assert.Equal(blocks[8], tc2.Try);
Assert.Equal(blocks[9], tc2.Catch);
Assert.Empty(tc2.Try.Predecessors);
- Assert.Empty(tc2.Catch.Predecessors);
+ Assert.Empty(tc2.Catch!.Predecessors);
Assert.Single(blocks[8].Successors);
Assert.IsType(blocks[8].Successors[0]);
Assert.Single(blocks[9].Successors);
diff --git a/UnderanalyzerTest/VMAssembly.ParseAssemblyFromLines.cs b/UnderanalyzerTest/VMAssembly.ParseAssemblyFromLines.cs
index c6f68d9..1f40e0c 100644
--- a/UnderanalyzerTest/VMAssembly.ParseAssemblyFromLines.cs
+++ b/UnderanalyzerTest/VMAssembly.ParseAssemblyFromLines.cs
@@ -143,14 +143,14 @@ push.i [variable]test_variable_reference
Assert.Equal(IGMInstruction.DataType.Variable, list[26].Type2);
Assert.Equal(IGMInstruction.InstanceType.Self, list[26].InstType);
Assert.Equal(IGMInstruction.VariableType.Normal, list[26].ReferenceVarType);
- Assert.Equal("a", list[26].Variable.Name.Content);
+ Assert.Equal("a", list[26].Variable!.Name.Content);
Assert.Equal(IGMInstruction.Opcode.Pop, list[29].Kind);
Assert.Equal(IGMInstruction.DataType.Variable, list[29].Type1);
Assert.Equal(IGMInstruction.DataType.Variable, list[29].Type2);
Assert.Equal(IGMInstruction.InstanceType.Self, list[29].InstType);
Assert.Equal(IGMInstruction.VariableType.StackTop, list[29].ReferenceVarType);
- Assert.Equal("a", list[29].Variable.Name.Content);
+ Assert.Equal("a", list[29].Variable!.Name.Content);
Assert.Equal(IGMInstruction.Opcode.Branch, list[41].Kind);
Assert.Equal(-list[41].Address, list[41].BranchOffset);
@@ -160,25 +160,25 @@ push.i [variable]test_variable_reference
Assert.Equal(IGMInstruction.Opcode.Push, list[56].Kind);
Assert.Equal(IGMInstruction.DataType.String, list[56].Type1);
- Assert.Equal("Test string!", list[56].ValueString.Content);
+ Assert.Equal("Test string!", list[56].ValueString!.Content);
Assert.Equal(IGMInstruction.Opcode.Push, list[57].Kind);
Assert.Equal(IGMInstruction.DataType.String, list[57].Type1);
- Assert.Equal("\"Test\nescaped\nstring!\"", list[57].ValueString.Content);
+ Assert.Equal("\"Test\nescaped\nstring!\"", list[57].ValueString!.Content);
Assert.Equal(IGMInstruction.Opcode.Push, list[72].Kind);
Assert.Equal(IGMInstruction.DataType.Int32, list[72].Type1);
- Assert.Equal("test_function_reference", list[72].Function.Name.Content);
+ Assert.Equal("test_function_reference", list[72].Function!.Name.Content);
Assert.Equal(IGMInstruction.Opcode.Extended, list[73].Kind);
Assert.Equal(IGMInstruction.ExtendedOpcode.PushReference, list[73].ExtKind);
Assert.Equal(IGMInstruction.DataType.Int32, list[73].Type1);
Assert.NotNull(list[73].Function);
- Assert.Equal("test_function_reference", list[73].Function.Name.Content);
+ Assert.Equal("test_function_reference", list[73].Function!.Name.Content);
Assert.Equal(IGMInstruction.Opcode.Push, list[74].Kind);
Assert.Equal(IGMInstruction.DataType.Int32, list[74].Type1);
- Assert.Equal("test_variable_reference", list[74].Variable.Name.Content);
+ Assert.Equal("test_variable_reference", list[74].Variable!.Name.Content);
}
[Fact]