Scope-Aware Identifier Renaming
Identifier renaming is the most fundamental AST transform. A naive renamer that walks the AST and changes everyIdentifier node would break your code immediately, because it would rename globals, string contents, and field names indiscriminately. A proper renamer builds a scope tree and only renames local variables.
Building the Scope Tree
Each block-creating construct in Lua introduces a new scope:do ... end, function(...) ... end, for ... do ... end, while ... do ... end, and repeat ... until .... The scope tree builder walks the AST and creates a symbol table for each scope.
The Renamer Visitor
Once the scope tree is built, a second visitor walks the AST and applies renames. For eachNameExpression, it walks up the scope chain to find which scope declared that name, then substitutes the renamed version.
Upvalue Handling
Closures capture references to variables from outer scopes. These are called upvalues. When you rename a local in an outer scope, every closure that captures it must use the same renamed name. The scope tree handles this naturally: all closures share the same symbol table reference for their parent scope, so a rename in the parent scope propagates to all child scopes automatically.NameExpression for outer inside closure resolves to the same scope entry as the LocalStatement that declared it. The renamer replaces both with the same generated name.
Expression Decomposition
Complex expressions can be split into multiple statements using temporary variables. This breaks up readable arithmetic and makes data flow harder to trace.Transform Pattern
AST Transform Steps
- Detect
BinaryExpressionnodes where either operand is itself aBinaryExpression - Extract the left sub-expression into a new
LocalStatementwith a generated temporary name - Extract the right sub-expression into another
LocalStatement - Replace the original operands with
NameExpressionnodes referencing the temporaries
Statement Reordering
Independent statements (those with no data dependencies between them) can be shuffled to make the logical flow harder to follow.Detecting Data Dependencies
For each statement, compute two sets:- Writes: the set of variables assigned by this statement
- Reads: the set of variables read by this statement
Dependency Analysis Implementation
Function Inlining
Function inlining replaces a call to a small function with the function’s body directly at the call site. This removes the function from the source entirely, making call graphs harder to reconstruct.Before and After
Advanced Inline with Scope Isolation
To avoid name conflicts, the inlined body should use fresh temporary names for all its locals:- Locate
CallExpressionnodes where the callee is a known local function - Clone the function body AST
- Rename all parameters and locals in the cloned body to fresh names
- Replace the
CallExpressionwith an immediately-invokedFunctionExpression - Map the original call arguments to the renamed parameters
Function Outlining
Function outlining is the inverse of inlining. You extract a sequence of statements into a new anonymous function and replace the original statements with a call to that function.Before and After
Local-to-Upvalue Promotion
Wrapping the entire script body in a closure converts all top-level locals into upvalues of the inner function. This makes them invisible to global introspection tools and debuggers that scan the global environment.The Wrapping Pattern
AST Transform
- Wrap the root
Chunkbody in aFunctionExpressionwith no parameters - Wrap that function in a
CallExpression(immediate invocation) - Replace the original
Chunkbody with a singleLocalStatementorAssignmentStatementthat captures the result
Numeric For to While Transformation
Convertingfor loops to while loops removes a structured control flow construct and replaces it with a more verbose, manually-managed loop.
Transform Pattern
AST Transform Steps
- Detect
NumericForStatementnodes - Emit a
LocalStatementfor the counter variable initialized to the start expression - Emit a
WhileStatementwith conditioncounter <= stop(or>=for negative step) - Insert the original loop body into the
WhileStatement’s body - Append an
AssignmentStatementat the end of the loop body to increment the counter by the step value
When the step expression is not a compile-time constant, the transform must emit a conditional increment or handle both positive and negative step directions.
Table Constructor Splitting
Table constructors can be split into a series of assignment statements. This destroys the visual grouping of related data and introduces intermediate states.Transform Pattern
Optional Shuffling
After splitting, the assignment statements can be shuffled if they have no data dependencies:- Detect
TableConstructornodes with named fields - Replace the constructor with an empty table
{} - For each field, emit an
AssignmentStatementusingFieldExpressionindexing
String Concatenation Splitting
String literals can be split into runtime-assembled fragments to hide their contents from static analysis.Transform Pattern
StringLiteral nodes with either:
- A
CallExpressiontostring.subon a larger garbage-padded string - A chain of
BinaryExpressionnodes using..withCallExpressionnodes tostring.char
Pipeline Ordering
Recommended pipeline order:1
Scope Analysis
Build the scope tree and symbol tables before any renaming occurs.
2
Identifier Renaming
Rename all local variables while scope information is clean and unmodified.
3
Expression Decomposition
Split complex expressions after renaming, so temporaries also get obfuscated names.
4
Statement Reordering
Shuffle independent statements after decomposition introduces new temporaries.
5
Control Flow Restructuring
Apply for-to-while transforms and function outlining after the basic structure is stable.
6
String Encoding
Replace string literals with runtime decoding expressions as a final pass.
Running expression decomposition before statement reordering is important because decomposition introduces new temporaries that create data dependencies, which the reordering pass must respect.
Next Steps
Now that you understand the core AST transformations, explore the tools and libraries that make them practical:AST Tools
Discover parsers and obfuscation frameworks for Lua and Luau.
Variable Renaming
Deep dive into identifier renaming strategies and edge cases.
Control Flow
Learn additional control flow obfuscation techniques.
