Skip to content

Commit

Permalink
Add cleanup for builtin array variables
Browse files Browse the repository at this point in the history
  • Loading branch information
colinator27 committed Jul 14, 2024
1 parent 5f86e7a commit 606a9f6
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
22 changes: 19 additions & 3 deletions Underanalyzer/Decompiler/AST/Nodes/VariableNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,26 @@ public bool SimilarToInForIncrementor(VariableNode other)

public IExpressionNode Clean(ASTCleaner cleaner)
{
// Clean up left side of variable, and get basic instance type, or 0 if none
Left = Left.Clean(cleaner);
int instType = (Left as Int16Node)?.Value ?? (int?)((Left as InstanceTypeNode)?.InstanceType) ?? 0;

// Check if we're a builtin array variable, and if so, rewrite with no array accessor if
// a 64-bit zero value is supplied as an index. This is a GML compiler quirk.
if (instType == (int)InstanceType.Self || instType == (int)InstanceType.Builtin)
{
if (ArrayIndices is [Int64Node { Value: 0 }] &&
VMConstants.BuiltinArrayVariables.Contains(Variable.Name.Content))
{
// This is most likely a compiler-generated array access; get rid of it
if (cleaner.Context.Settings.CleanupBuiltinArrayVariables)
{
ArrayIndices = null;
}
}
}

// Clean up array indices
if (ArrayIndices is not null)
{
for (int i = 0; i < ArrayIndices.Count; i++)
Expand All @@ -209,9 +228,6 @@ public IExpressionNode Clean(ASTCleaner cleaner)
{
Left.Group = true;
}

// Get basic instance type, or 0 if none
int instType = (Left as Int16Node)?.Value ?? (int?)((Left as InstanceTypeNode)?.InstanceType) ?? 0;

// Check if we're a struct argument
if (cleaner.StructArguments is not null)
Expand Down
13 changes: 10 additions & 3 deletions Underanalyzer/Decompiler/DecompileSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,27 @@ public interface IDecompileSettings
public bool RemoveSingleLineBlockBraces { get; }

/// <summary>
/// Whether try/catch/finally statements should have their compiler-generated control flow cleaned up.
/// True if try/catch/finally statements should have their compiler-generated control flow cleaned up.
/// This cleanup can occasionally be inaccurate to the code that actually executes, due to multiple compiler bugs.
/// </summary>
public bool CleanupTry { get; }

/// <summary>
/// Whether empty if/else chains at the end of a loop body should be rewritten as continue statements, when possible.
/// True if empty if/else chains at the end of a loop body should be rewritten as continue statements, when possible.
/// </summary>
public bool CleanupElseToContinue { get; }

/// <summary>
/// Whether GMLv2 default argument values in functions should be detected and cleaned up.
/// True if GMLv2 default argument values in functions should be detected and cleaned up.
/// </summary>
public bool CleanupDefaultArgumentValues { get; }

/// <summary>
/// True if built-in array variables used without an array accessor should be rewritten as such,
/// rather than using the compiler-generated array accessor at index 0.
/// </summary>
public bool CleanupBuiltinArrayVariables { get; }

/// <summary>
/// If true, enum values that are detected in a code entry (including any unknown ones) will
/// be given declarations at the top of the code.
Expand Down Expand Up @@ -171,6 +177,7 @@ public class DecompileSettings : IDecompileSettings
public bool CleanupTry { get; set; } = true;
public bool CleanupElseToContinue { get; set; } = true;
public bool CleanupDefaultArgumentValues { get; set; } = true;
public bool CleanupBuiltinArrayVariables { get; set; } = true;
public bool CreateEnumDeclarations { get; set; } = true;
public string UnknownEnumName { get; set; } = "UnknownEnum";
public string UnknownEnumValuePattern { get; set; } = "Value_{0}";
Expand Down
27 changes: 27 additions & 0 deletions Underanalyzer/VMConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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 System.Collections.Generic;

namespace Underanalyzer;

/// <summary>
Expand Down Expand Up @@ -54,4 +56,29 @@ internal static class VMConstants
// Special-case GML functions used during macro resolution
public const string ChooseFunction = "choose";
public const string ScriptExecuteFunction = "script_execute";

// Builtin array variables (some of which don't exist past GMS2, but are still recognized by the compiler apparently)
public static readonly HashSet<string> BuiltinArrayVariables =
[
"view_xview",
"view_yview",
"view_wview",
"view_hview",
"view_angle",
"view_hborder",
"view_vborder",
"view_hspeed",
"view_vspeed",
"view_object",
"view_xport",
"view_yport",
"view_wport",
"view_hport",
"view_surface_id",
"view_camera",
"phy_collision_x",
"phy_collision_y",
"phy_col_normal_x",
"phy_col_normal_y"
];
}
22 changes: 22 additions & 0 deletions UnderanalyzerTest/DecompileContext.DecompileToString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2315,4 +2315,26 @@ b [0]
"""
);
}

[Fact]
public void TestNoAccessorBuiltinArray()
{
TestUtil.VerifyDecompileResult(
"""
pushi.e -1
push.l 0
conv.l.i
push.v [array]self.view_xview
pop.v.v self.a
pushi.e -1
pushi.e 0
push.v [array]self.view_xview
pop.v.v self.a
""",
"""
a = view_xview;
a = view_xview[0];
"""
);
}
}

0 comments on commit 606a9f6

Please sign in to comment.