> ## Documentation Index
> Fetch the complete documentation index at: https://starcryptdocs.starsolenterprise.com/lua-docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Control Flow Obfuscation in Lua/Luau

> Disguise the logical structure of Lua and Luau scripts through control flow flattening, opaque predicates, goto spaghetti, dispatcher loops, and branch injection.

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

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function calculateDiscount(price, isMember)
    local discount = 0
    if isMember then
        discount = price * 0.1
    else
        discount = price * 0.05
    end
    return discount
end
```

### After: Flattened Dispatcher

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function calculateDiscount(price, isMember)
    local state = 1
    local discount = 0
    while true do
        if state == 1 then
            if isMember then
                state = 2
            else
                state = 3
            end
        elseif state == 2 then
            discount = price * 0.1
            state = 4
        elseif state == 3 then
            discount = price * 0.05
            state = 4
        elseif state == 4 then
            return discount
        end
    end
end
```

### 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

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Random state numbers with shuffled textual order
while true do
    if state == 89 then      -- was state 3 (else branch)
        discount = price * 0.05
        state = 3
    elseif state == 47 then  -- was state 1 (entry)
        if isMember then
            state = 12
        else
            state = 89
        end
    elseif state == 12 then  -- was state 2 (then branch)
        discount = price * 0.1
        state = 3
    elseif state == 3 then     -- was state 4 (return)
        return discount
    end
end
```

### 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.

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function complexFunction(a, b)
    local outerState = 1
    local result = 0
    while true do
        if outerState == 1 then
            -- flatten the inner loop too
            local innerState = 1
            while true do
                if innerState == 1 then
                    if a > b then
                        innerState = 2
                    else
                        innerState = 3
                    end
                elseif innerState == 2 then
                    result = a - b
                    innerState = 4
                elseif innerState == 3 then
                    result = b - a
                    innerState = 4
                elseif innerState == 4 then
                    break
                end
            end
            outerState = 2
        elseif outerState == 2 then
            return result * 2
        end
    end
end
```

## 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

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function processValue(x)
    local result
    ::blk_1::
    if x > 0 then
        goto blk_2
    else
        goto blk_3
    end

    ::blk_2::
    result = x * 2
    goto blk_4

    ::blk_3::
    result = x + 10
    goto blk_4

    ::blk_4::
    return result
end
```

### Interspersing Dead Labels

Add labels that are never jumped to. They appear as valid targets but are actually unreachable.

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function processValue(x)
    local result
    ::blk_1::
    ::dead_1::
    if x > 0 then
        goto blk_2
    else
        goto blk_3
    end

    ::dead_2::
    result = 999999  -- never executed

    ::blk_2::
    ::dead_3::
    result = x * 2
    goto blk_4

    ::blk_3::
    result = x + 10
    goto blk_4

    ::dead_4::
    ::dead_5::

    ::blk_4::
    return result
end
```

> [!WARNING]
>
> 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

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Always true
if (x * 0 == 0) then
    realCode()
else
    deadCode()  -- never runs
end

-- Always true
if (math.type and true or true) then
    realCode()
end

-- Always true
if (#"" == 0) then
    realCode()
end
```

### Trivially False Predicates

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Always false
if (1 == 2) then
    deadCode()
else
    realCode()
end

-- Always false
if (math.huge < 0) then
    deadCode()
else
    realCode()
end
```

### Algebraic Identity Predicates

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Always true for any integer n
local n = 42
if (n % 2 == n % 2) then
    realCode()
else
    deadCode()
end

-- Always true: any number squared is non-negative
if (x * x >= 0) then
    realCode()
end

-- Always true: string length is always >= 0
if (#tostring(x) >= 0) then
    realCode()
end
```

### Inserting Opaque Predicates

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function authenticate(token)
    -- Opaque predicate: always true
    if (math.type and true or true) then
        -- real logic
        return token == "secret"
    else
        -- dead branch with decoy logic
        return validateLegacyToken(token)
    end
end
```

## 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

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function computeHash(data)
    local hash = 0
    if (math.huge < 0) then  -- always false
        -- decoy: looks like real hashing
        for i = 1, #data do
            hash = hash + string.byte(data, i) * 31
        end
        hash = hash % 65536
    else
        -- real logic
        for i = 1, #data do
            hash = bit32.bxor(hash * 31, string.byte(data, i))
        end
    end
    return hash
end
```

### Always-True Branches

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function fetchConfig()
    if (1 == 1) then  -- always true
        return loadConfigFromServer()
    else
        -- dead code that looks like a fallback
        return loadConfigFromCache()
    end
end
```

## Loop Transformation

Transform recognizable loops into unusual forms.

### Numeric For to While Loop

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Before
for i = 1, 10 do
    print(i)
end

-- After
local i = 1
while i <= 10 do
    print(i)
    i = i + 1
end
```

### Numeric For to Generic For with Custom Iterator

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Custom iterator that behaves like a numeric loop
local function range(n)
    local i = 0
    return function()
        i = i + 1
        if i <= n then
            return i
        end
    end
end

-- After
for i in range(10) do
    print(i)
end
```

### Wrapping in Repeat/Until

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
repeat
    -- real code wrapped unnecessarily
    for i = 1, 10 do
        print(i)
    end
until true  -- always executes exactly once
```

### Break with Opaque Predicates

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
for i = 1, 100 do
    if (math.huge < 0) then  -- never true
        break  -- looks like loop might exit early
    end
    processItem(i)
end
```

## Expression Splitting and Reordering

### Splitting Compound Expressions

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Before
if (x > 0 and y < 100 and z ~= nil) then

-- After
local cond1 = x > 0
local cond2 = y < 100
local cond3 = z ~= nil
local allCond = cond1 and cond2 and cond3
if allCond then
```

### Reversing Condition Logic

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Before
if enabled then

-- After
if not (not enabled) then

-- Double negative with opaque predicate
if not (not (enabled and (1 == 1))) then
```

### Reordering Independent Statements

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
-- Before: logical narrative flow
local a = 1
local b = 2
local c = a + b
print(c)

-- After: scrambled order
local b = 2
local temp = print
local a = 1
local c = a + b
temp(c)
```

> [!TIP]
>
> 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.

> [!NOTE]
>
> 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.

> **Does control flow obfuscation affect performance?**
>
> 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.