From a96848d830f68e0bdf70b5b701b0c4843618d381 Mon Sep 17 00:00:00 2001 From: colinator27 <17358554+colinator27@users.noreply.github.com> Date: Tue, 9 Jul 2024 21:49:03 -0400 Subject: [PATCH] Enable nullables on main library, and cleanup --- Underanalyzer/Decompiler/AST/ASTBuilder.cs | 49 +++--- Underanalyzer/Decompiler/AST/ASTCleaner.cs | 19 +- .../Decompiler/AST/ASTFragmentContext.cs | 42 +++-- Underanalyzer/Decompiler/AST/ASTPrinter.cs | 23 +-- .../Decompiler/AST/BlockSimulator.cs | 108 ++++++------ Underanalyzer/Decompiler/AST/IFragmentNode.cs | 12 +- .../Decompiler/AST/IFunctionCallNode.cs | 4 +- .../Decompiler/AST/IMacroResolvableNode.cs | 4 +- .../Decompiler/AST/IMacroTypeNode.cs | 4 +- .../Decompiler/AST/Nodes/ArrayInitNode.cs | 11 +- .../AST/Nodes/AssetReferenceNode.cs | 25 +-- .../Decompiler/AST/Nodes/AssignNode.cs | 20 +-- .../Decompiler/AST/Nodes/BinaryNode.cs | 4 +- .../AST/Nodes/BlockLocalVarDeclNode.cs | 2 +- .../Decompiler/AST/Nodes/BlockNode.cs | 13 +- .../Decompiler/AST/Nodes/BooleanNode.cs | 11 +- .../Decompiler/AST/Nodes/ConditionalNode.cs | 18 +- .../Decompiler/AST/Nodes/DoUntilLoopNode.cs | 12 +- .../Decompiler/AST/Nodes/DoubleNode.cs | 13 +- .../Decompiler/AST/Nodes/EnumDeclNode.cs | 9 +- .../Decompiler/AST/Nodes/EnumValueNode.cs | 23 +-- .../Decompiler/AST/Nodes/ExitNode.cs | 2 +- .../Decompiler/AST/Nodes/ForLoopNode.cs | 29 ++-- .../Decompiler/AST/Nodes/FunctionCallNode.cs | 17 +- .../Decompiler/AST/Nodes/FunctionDeclNode.cs | 42 ++--- .../AST/Nodes/FunctionReferenceNode.cs | 11 +- Underanalyzer/Decompiler/AST/Nodes/IfNode.cs | 17 +- .../Decompiler/AST/Nodes/InstanceTypeNode.cs | 11 +- .../Decompiler/AST/Nodes/Int16Node.cs | 15 +- .../Decompiler/AST/Nodes/Int32Node.cs | 12 +- .../Decompiler/AST/Nodes/Int64Node.cs | 11 +- .../Decompiler/AST/Nodes/MacroValueNode.cs | 11 +- .../Decompiler/AST/Nodes/NewObjectNode.cs | 17 +- .../AST/Nodes/NullishCoalesceNode.cs | 14 +- .../AST/Nodes/PredefinedDoubleNode.cs | 21 +-- .../Decompiler/AST/Nodes/RepeatLoopNode.cs | 12 +- .../Decompiler/AST/Nodes/ReturnNode.cs | 17 +- .../Decompiler/AST/Nodes/ShortCircuitNode.cs | 12 +- .../Decompiler/AST/Nodes/StaticInitNode.cs | 11 +- .../Decompiler/AST/Nodes/StringNode.cs | 11 +- .../Decompiler/AST/Nodes/StructNode.cs | 14 +- .../Decompiler/AST/Nodes/SwitchCaseNode.cs | 13 +- .../Decompiler/AST/Nodes/SwitchNode.cs | 12 +- .../Decompiler/AST/Nodes/ThrowNode.cs | 11 +- .../Decompiler/AST/Nodes/TryCatchNode.cs | 51 +++--- .../Decompiler/AST/Nodes/UnaryNode.cs | 17 +- .../Decompiler/AST/Nodes/VariableCallNode.cs | 24 +-- .../Decompiler/AST/Nodes/VariableHashNode.cs | 11 +- .../Decompiler/AST/Nodes/VariableNode.cs | 82 +++++---- .../Decompiler/AST/Nodes/WhileLoopNode.cs | 15 +- .../Decompiler/AST/Nodes/WithLoopNode.cs | 12 +- .../Decompiler/ControlFlow/BinaryBranch.cs | 46 ++--- Underanalyzer/Decompiler/ControlFlow/Block.cs | 44 +++-- .../Decompiler/ControlFlow/Branches.cs | 30 ++-- .../Decompiler/ControlFlow/BreakNode.cs | 8 +- .../Decompiler/ControlFlow/ContinueNode.cs | 8 +- .../Decompiler/ControlFlow/ControlFlowNode.cs | 20 ++- .../Decompiler/ControlFlow/DoUntilLoop.cs | 12 +- .../Decompiler/ControlFlow/EmptyNode.cs | 8 +- .../Decompiler/ControlFlow/ExitNode.cs | 8 +- .../Decompiler/ControlFlow/Fragment.cs | 40 ++--- Underanalyzer/Decompiler/ControlFlow/Loop.cs | 32 ++-- .../Decompiler/ControlFlow/Nullish.cs | 20 +-- .../Decompiler/ControlFlow/RepeatLoop.cs | 16 +- .../Decompiler/ControlFlow/ReturnNode.cs | 8 +- .../Decompiler/ControlFlow/ShortCircuit.cs | 40 ++--- .../Decompiler/ControlFlow/StaticInit.cs | 14 +- .../Decompiler/ControlFlow/Switch.cs | 153 ++++++++-------- .../Decompiler/ControlFlow/TryCatch.cs | 66 ++++--- .../Decompiler/ControlFlow/WhileLoop.cs | 22 ++- .../Decompiler/ControlFlow/WithLoop.cs | 18 +- Underanalyzer/Decompiler/DecompileContext.cs | 45 ++--- Underanalyzer/Decompiler/DecompileSettings.cs | 7 +- .../Decompiler/GameSpecific/GMEnum.cs | 6 +- .../GameSpecific/GameSpecificRegistry.cs | 4 +- .../Decompiler/GameSpecific/IMacroType.cs | 20 +-- .../GameSpecific/IMacroTypeResolver.cs | 12 +- .../Json/ArrayInitMacroTypeConverter.cs | 2 +- .../Json/ConstantsMacroTypeConverter.cs | 8 +- .../Json/EnumMacroTypeConverter.cs | 14 +- .../Json/GameSpecificRegistryConverter.cs | 37 ++-- .../GameSpecific/Json/IMacroTypeConverter.cs | 21 +-- .../Json/IntersectMacroTypeConverter.cs | 4 +- .../Json/MatchMacroTypeConverter.cs | 10 +- .../Json/NameMacroTypeResolverConverter.cs | 6 +- .../Json/NamedArgumentResolverConverter.cs | 8 +- .../Json/NotMatchMacroTypeConverter.cs | 10 +- .../Json/UnionMacroTypeConverter.cs | 4 +- .../MacroTypes/ArrayInitMacroType.cs | 11 +- .../GameSpecific/MacroTypes/AssetMacroType.cs | 13 +- .../MacroTypes/BooleanMacroType.cs | 2 +- .../GameSpecific/MacroTypes/ColorMacroType.cs | 2 +- .../MacroTypes/ConditionalMacroType.cs | 34 ++-- .../MacroTypes/ConstantsMacroType.cs | 10 +- .../GameSpecific/MacroTypes/EnumMacroType.cs | 12 +- .../MacroTypes/FunctionArgsMacroType.cs | 16 +- .../MacroTypes/InstanceMacroType.cs | 2 +- .../MacroTypes/IntersectMacroType.cs | 20 +-- .../GameSpecific/MacroTypes/MatchMacroType.cs | 16 +- .../MacroTypes/MatchNotMacroType.cs | 16 +- .../GameSpecific/MacroTypes/UnionMacroType.cs | 20 +-- .../MacroTypes/VirtualKeyMacroType.cs | 4 +- .../Resolvers/GlobalMacroTypeResolver.cs | 20 +-- .../Resolvers/NameMacroTypeResolver.cs | 18 +- .../Resolvers/NamedArgumentResolver.cs | 8 +- Underanalyzer/Decompiler/GlobalFunctions.cs | 40 +++-- Underanalyzer/IGameContext.cs | 2 +- Underanalyzer/Mock/GameContextMock.cs | 18 +- Underanalyzer/Mock/VMAssemblyMock.cs | 163 ++++++++++++++---- Underanalyzer/Mock/VMDataMock.cs | 81 ++++----- Underanalyzer/Underanalyzer.csproj | 1 + Underanalyzer/VMData.cs | 55 +++--- .../BinaryBranch.FindBinaryBranches.Loops.cs | 14 +- .../BinaryBranch.FindBinaryBranches.Switch.cs | 78 ++++----- .../BinaryBranch.FindBinaryBranches.cs | 18 +- UnderanalyzerTest/Loop.FindLoops.cs | 16 +- UnderanalyzerTest/TestUtil.cs | 4 +- UnderanalyzerTest/TryCatch.FindTryCatch.cs | 8 +- .../VMAssembly.ParseAssemblyFromLines.cs | 14 +- 119 files changed, 1189 insertions(+), 1359 deletions(-) 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]