Skip to content

Commit

Permalink
Enable nullables on main library, and cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
colinator27 committed Jul 10, 2024
1 parent ab5e1a4 commit a96848d
Show file tree
Hide file tree
Showing 119 changed files with 1,189 additions and 1,359 deletions.
49 changes: 22 additions & 27 deletions Underanalyzer/Decompiler/AST/ASTBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ This Source Code Form is subject to the terms of the Mozilla Public

namespace Underanalyzer.Decompiler.AST;

public class ASTBuilder
/// <summary>
/// Manages the building of a high-level AST from control flow nodes.
/// </summary>
public class ASTBuilder(DecompileContext context)
{
/// <summary>
/// The corresponding code context for this AST builder.
/// </summary>
public DecompileContext Context { get; }
public DecompileContext Context { get; } = context;

/// <summary>
/// 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.
/// </summary>
internal Stack<IExpressionNode> ExpressionStack { get => TopFragmentContext.ExpressionStack; }
internal Stack<IExpressionNode> ExpressionStack { get => TopFragmentContext!.ExpressionStack; }

/// <summary>
/// The index to start processing instructions for the next ControlFlow.Block we encounter.
Expand All @@ -31,17 +34,17 @@ public class ASTBuilder
/// <summary>
/// List of arguments passed into a struct fragment.
/// </summary>
internal List<IExpressionNode> StructArguments { get => TopFragmentContext.StructArguments; set => TopFragmentContext.StructArguments = value; }
internal List<IExpressionNode>? StructArguments { get => TopFragmentContext!.StructArguments; set => TopFragmentContext!.StructArguments = value; }

/// <summary>
/// Set of all local variables present in the current fragment.
/// </summary>
internal HashSet<string> LocalVariableNames { get => TopFragmentContext.LocalVariableNames; }
internal HashSet<string> LocalVariableNames { get => TopFragmentContext!.LocalVariableNames; }

/// <summary>
/// Set of all local variables present in the current fragment.
/// </summary>
internal List<string> LocalVariableNamesList { get => TopFragmentContext.LocalVariableNamesList; }
internal List<string> LocalVariableNamesList { get => TopFragmentContext!.LocalVariableNamesList; }

/// <summary>
/// The stack used to manage fragment contexts.
Expand All @@ -51,28 +54,20 @@ public class ASTBuilder
/// <summary>
/// The current/top fragment context.
/// </summary>
internal ASTFragmentContext TopFragmentContext { get; private set; }
internal ASTFragmentContext? TopFragmentContext { get; private set; }

/// <summary>
/// Current queue of switch case expressions.
/// </summary>
internal Queue<IExpressionNode> SwitchCases { get; set; } = null;

/// <summary>
/// Initializes a new AST builder from the given code context.
/// </summary>
public ASTBuilder(DecompileContext context)
{
Context = context;
}
internal Queue<IExpressionNode>? SwitchCases { get; set; } = null;

/// <summary>
/// Builds the AST for an entire code entry, starting from the root fragment node.
/// </summary>
public IStatementNode Build()
{
List<IStatementNode> output = new(1);
PushFragmentContext(Context.FragmentNodes[0]);
PushFragmentContext(Context.FragmentNodes![0]);
Context.FragmentNodes[0].BuildAST(this, output);
PopFragmentContext();
return output[0];
Expand All @@ -81,7 +76,7 @@ public IStatementNode Build()
/// <summary>
/// Returns the control flow node following the given control flow node, in the current block.
/// </summary>
private IControlFlowNode Follow(IControlFlowNode node)
private static IControlFlowNode? Follow(IControlFlowNode node)
{
// Ensure we follow a linear path
if (node.Successors.Count > 1)
Expand Down Expand Up @@ -109,9 +104,9 @@ private IControlFlowNode Follow(IControlFlowNode node)
/// <summary>
/// Builds a block starting from a control flow node, following all of its successors linearly.
/// </summary>
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;
Expand All @@ -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.
/// </summary>
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;
Expand All @@ -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<IStatementNode> expressionOutput = new();
private readonly List<IStatementNode> expressionOutput = [];

/// <summary>
/// Builds an expression (of unknown type) starting from a control flow node,
/// following all of its successors linearly.
/// </summary>
internal IExpressionNode BuildExpression(IControlFlowNode startNode, List<IStatementNode> output = null)
internal IExpressionNode BuildExpression(IControlFlowNode? startNode, List<IStatementNode>? output = null)
{
output ??= expressionOutput;
int stackCountBefore = ExpressionStack.Count;
Expand Down Expand Up @@ -200,7 +195,7 @@ internal IExpressionNode BuildExpression(IControlFlowNode startNode, List<IState
/// No statements can be created in this context, and at most a defined number of expressions can be created,
/// or -1 if any number of expressions can be created.
/// </summary>
internal void BuildArbitrary(IControlFlowNode startNode, List<IStatementNode> output = null, int numAllowedExpressions = 0)
internal void BuildArbitrary(IControlFlowNode? startNode, List<IStatementNode>? output = null, int numAllowedExpressions = 0)
{
output ??= expressionOutput;
int stackCountBefore = ExpressionStack.Count;
Expand Down Expand Up @@ -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 ?? "<unknown code entry>"));
}
else
{
Expand All @@ -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;
}
}

Expand Down
19 changes: 7 additions & 12 deletions Underanalyzer/Decompiler/AST/ASTCleaner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ namespace Underanalyzer.Decompiler.AST;
/// <summary>
/// Manages cleaning/postprocessing of the AST.
/// </summary>
public class ASTCleaner
public class ASTCleaner(DecompileContext context)
{
/// <summary>
/// The decompilation context this is cleaning for.
/// </summary>
public DecompileContext Context { get; }
public DecompileContext Context { get; } = context;

/// <summary>
/// List of arguments passed into a struct fragment.
/// </summary>
internal List<IExpressionNode> StructArguments { get => TopFragmentContext.StructArguments; set => TopFragmentContext.StructArguments = value; }
internal List<IExpressionNode>? StructArguments { get => TopFragmentContext!.StructArguments; set => TopFragmentContext!.StructArguments = value; }

/// <summary>
/// Set of all local variables present in the current fragment.
/// </summary>
internal HashSet<string> LocalVariableNames { get => TopFragmentContext.LocalVariableNames; }
internal HashSet<string> LocalVariableNames { get => TopFragmentContext!.LocalVariableNames; }

/// <summary>
/// The stack used to manage fragment contexts.
Expand All @@ -37,7 +37,7 @@ public class ASTCleaner
/// <summary>
/// The current/top fragment context.
/// </summary>
internal ASTFragmentContext TopFragmentContext { get; private set; }
internal ASTFragmentContext? TopFragmentContext { get; private set; }

/// <summary>
/// Helper to access the global macro resolver used for resolving macro types.
Expand All @@ -47,7 +47,7 @@ public class ASTCleaner
/// <summary>
/// Helper to access an ID instance and object type union, for resolving macro types.
/// </summary>
internal IMacroType MacroInstanceIdOrObjectAsset
internal IMacroType? MacroInstanceIdOrObjectAsset
{
get
{
Expand All @@ -63,12 +63,7 @@ internal IMacroType MacroInstanceIdOrObjectAsset
return _macroInstanceIdOrObjectAsset;
}
}
private IMacroType _macroInstanceIdOrObjectAsset = null;

public ASTCleaner(DecompileContext context)
{
Context = context;
}
private IMacroType? _macroInstanceIdOrObjectAsset = null;

/// <summary>
/// Pushes a context onto the fragment context stack.
Expand Down
42 changes: 24 additions & 18 deletions Underanalyzer/Decompiler/AST/ASTFragmentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ public class ASTFragmentContext
/// <summary>
/// The name of the code entry this fragment belongs to.
/// </summary>
public string CodeEntryName { get => Fragment.CodeEntry.Name?.Content; }
public string? CodeEntryName { get => Fragment.CodeEntry.Name?.Content; }

/// <summary>
/// The name of the function this fragment belongs to, or null if none.
/// The name of the function this fragment belongs to, or <see langword="null"/> if none.
/// </summary>
public string FunctionName { get; internal set; } = null;
public string? FunctionName { get; internal set; } = null;

/// <summary>
/// Children of this fragment, e.g. sub-functions.
/// </summary>
internal List<ASTFragmentContext> Children { get; } = new();
internal List<ASTFragmentContext> Children { get; } = [];

/// <summary>
/// Current working VM expression stack.
Expand All @@ -47,39 +47,39 @@ public class ASTFragmentContext
/// <summary>
/// If not null, represents the list of arguments getting passed into this fragment (which is a struct).
/// </summary>
public List<IExpressionNode> StructArguments { get; internal set; } = null;
public List<IExpressionNode>? StructArguments { get; internal set; } = null;

/// <summary>
/// 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 <see langword="null"/> otherwise.
/// </summary>
internal IExpressionNode BaseParentCall { get; set; } = null;
internal IExpressionNode? BaseParentCall { get; set; } = null;

/// <summary>
/// Contains all local variables referenced from within this fragment.
/// </summary>
public HashSet<string> LocalVariableNames { get; } = new();
public HashSet<string> LocalVariableNames { get; } = [];

/// <summary>
/// Contains all local variables referenced from within this fragment, in order of occurrence.
/// </summary>
public List<string> LocalVariableNamesList { get; } = new();
public List<string> LocalVariableNamesList { get; } = [];

/// <summary>
/// Map of code entry names to function names, for all children fragments/sub-functions of this context.
/// </summary>
public Dictionary<string, string> SubFunctionNames { get; } = new();
public Dictionary<string, string> SubFunctionNames { get; } = [];

/// <summary>
/// The loop surrounding the currently-building position in the AST.
/// </summary>
internal Loop SurroundingLoop { get; set; } = null;
internal Loop? SurroundingLoop { get; set; } = null;

/// <summary>
/// Contains local variable names that should be entirely removed from the fragment.
/// (For removing compiler-generated code.)
/// </summary>
internal HashSet<string> LocalVariablesToPurge { get; } = new();
internal HashSet<string> LocalVariablesToPurge { get; } = [];

/// <summary>
/// Stack of the number of statements contained in all enveloping try finally blocks.
Expand All @@ -94,12 +94,12 @@ public class ASTFragmentContext
/// <summary>
/// Contains all named argument variables referenced from within this fragment.
/// </summary>
internal HashSet<string> NamedArguments { get; set; } = new();
internal HashSet<string> NamedArguments { get; set; } = [];

/// <summary>
/// Lookup of argument index to argument name, for GMLv2 named arguments.
/// </summary>
private Dictionary<int, string> NamedArgumentByIndex { get; set; } = new();
private Dictionary<int, string> NamedArgumentByIndex { get; set; } = [];

internal ASTFragmentContext(Fragment fragment)
{
Expand Down Expand Up @@ -127,9 +127,9 @@ internal void RemoveLocal(string name)
/// <summary>
/// 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 <see langword="null"/> if prior to GMLv2 (and no named argument should be used).
/// </summary>
internal string GetNamedArgumentName(DecompileContext context, int index)
internal string? GetNamedArgumentName(DecompileContext context, int index)
{
// GMLv2 introduced named arguments
if (!context.GMLv2)
Expand All @@ -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);
Expand Down
Loading

0 comments on commit a96848d

Please sign in to comment.