Skip to main content
Control flow obfuscation targets static analysis tools and decompilers by making the execution path hard to follow. Instead of readable if/else chains and nested loops, the script becomes a maze of indirect jumps, dispatcher loops, and meaningless branches. This page covers the most effective control flow techniques for Lua and Luau, with full code examples you can adapt.

Control Flow Flattening

The classic flattening technique replaces structured control flow with a single while true do loop and a numeric state variable. Each basic block becomes a case in a large if/elseif chain inside the loop.

Before: Simple Sequential Code

After: Flattened Dispatcher

Assigning State Numbers

You can assign state numbers in different ways to confuse analysis:
  • Sequential: 1, 2, 3, 4 — simplest, still effective
  • Random: 47, 12, 89, 3 — harder to guess execution order
  • Shuffled: assign states so the textual order does not match execution order

Multi-Level Flattening

For deeply nested functions, use nested dispatchers. The outer dispatcher handles top-level blocks, and inner functions or blocks can themselves be flattened.

Goto Spaghetti (Lua 5.2+)

Lua 5.2 introduced goto and ::label:: syntax. You can abuse this to create spaghetti code that jumps around unpredictably.

Converting a Simple Function to Goto Spaghetti

Interspersing Dead Labels

Add labels that are never jumped to. They appear as valid targets but are actually unreachable.
Luau does not support goto. Use the dispatcher pattern for Roblox scripts. If you are targeting standard Lua 5.2+ or LuaJIT with the 5.2 extensions, goto spaghetti is highly effective against decompilers that expect structured control flow.

Opaque Predicates

An opaque predicate is a condition that always evaluates to true or false, but looks complex enough that a static analyzer cannot determine this.

Trivially True Predicates

Trivially False Predicates

Algebraic Identity Predicates

Inserting Opaque Predicates

Dead Branch Injection

Inject branches that are always taken or never taken, filling the unused path with plausible-looking code.

Always-False Branches with Decoy Code

Always-True Branches

Loop Transformation

Transform recognizable loops into unusual forms.

Numeric For to While Loop

Numeric For to Generic For with Custom Iterator

Wrapping in Repeat/Until

Break with Opaque Predicates

Expression Splitting and Reordering

Splitting Compound Expressions

Reversing Condition Logic

Reordering Independent Statements

Combine flattening with opaque predicates for maximum effect. Make each state transition check an opaque predicate so the decompiler cannot easily determine which state follows which.
Goto-based spaghetti is highly effective against decompilers that expect structured control flow. Many Lua decompilers simply fail or produce nonsense when faced with heavy goto usage.
Yes. Flattening adds a dispatch overhead per block: every block transition requires a loop iteration, a state comparison, and a state variable update. For hot code paths called thousands of times per frame, this overhead is measurable. Benchmark critical sections and consider leaving performance-sensitive inner loops unflattened, or apply flattening only to initialization and configuration code.