Skip to main content

closures

Library

Contains functions which interact and modify closures and their behaviour.

Functions

getfunctionhash

closures.getfunctionhash(
fn(T...) → U...,--

The function to hash.

hashType"SHA384" | "BLAKE2B" | "BLAKE3"--

The hash type, defaults to SHA384.

) → string--

The hex representation of the selected hashType hash of the function.

Provides a hash with a digest size of 48 bytes (384 bits) in hex format for the given luau function

Implementation

You must use the instructions and constants of the given function to obtain the hash.

Compiler

If your function is outputting different hashes every time its called, then you may have forgotten to account for Compiler settings. We recommend you read this post to know to resolve this issue and make your Luau compiler rSUNC compliant.

newcclosures/ newlclosure hashing

For newcclosures and newlclosures, the result of the hash must be come from the 'deepest' closure it wraps.

If such a closure is a C closure, then the function must error.

Errors

TypeDescription
the closure must resolve to a Luau closureA wrapper function was provided, but when resolving its root closure, it didn't point to a luau closure.
function must be a Luau closure.The first parameter was a C function instead of a Luau function.
Unsupported hash type.The given hash type is not supported by the executor.

hookfunction

closures.hookfunction(
fn(T...) → U...,--

Function to hook.

hook(T...) → U...--

The hook.

) → (T...) → U...--

The old/original function.

Hooks the given function with the desired hook. This function supports hooking all kinds of closures, from Lua to C, C to Lua, etc.

Implementations

Some implementations of this function may bypass upvalue limits for you. If this is not the case, wrap hook in newcclosure/newlclosure before calling hookfunction.

Restoring Hooked Functions

You can restore functions hooked with hookfunction using restorefunction, in case restorefunction is not available you may unhook by calling hookfunction with the first function being the hooked function, and the second being the original one.

Proto and savedpc

When a proto is hooked and it is on a callstack of any thread you must emit a clone of it, and set the func parameter of the callstack to the clone.

This will prevent a vulnerability where, upon returning from a yield or C code, the savedpc which was set with the original proto, attempts to resume execution in the new proto.

This causes: - Crashes in the form of Segmentation Faults - Line number error to be displayed improperly.

Among other things, you must either emit a clone of the proto or replace the callstack of that thread with the hooked one and perform all proper validation for it.

Example of line number being dislayed improperly if the function implementation is vulnerable: https://i.imgur.com/5sSfnut.png RbxStu V4 has fixed this issue already!

Vulnerability test code:

local bFinishedExecution = false
local bExecuted = false
local a 

local function b()
    print"b babyyy"
    print"b baby"
    print"b babyyy"
    restorefunction(a) -- may be affected as well, and you must apply similar fixes!
    warn("im b")
    bExecuted = true
end

a = function()
    local x = hookfunction(a, b)
    task.wait()
    print("I'm a")
    bFinishedExecution = true
    return x
end

local s, e = pcall(a)
assert(s, "savedpc [1]")
assert((bFinishedExecution and not bExecuted) or (not bFinishedExecution and bExecuted), "savedpc [2]")
print("no issues most likely")

--[[
    When hooking and suspending, the current instruction is saved into the threads' CallInfo->savedpc to resume. But as we have changed the Proto object, the address and offset of this savedpc, is NO LONGER VALID!
    Attempting to do anything with it would likely yield us either: a Luau C error with line number of the error in a completely invalid line, undefined behaviour and inexplicable crashes.

    This is a potential ACE that MUST be fixed and handled properly.
]]

Examples:

Testing hookfunction and restorefunction:

local function a()
    return "baa"
end
local function b()
    return "caa"
end

local aReturn = a()
local bReturn = b()
local hooked = closures.hookfunction(a, b)
local hookedReturn = hooked()
assert(hookedReturn == aReturn)
assert(a() == b() and a() == bReturn)
assert(hooked ~= a and b ~= a)
closures.restorefunction(hooked, a) -- Restore a again.
assert(a() == aReturn and a() ~= hookedReturn)

-- If this script errors, your hookfunction may not be working properly.

Example testing upvalue limits:

local a = "A"
local b = "B"
local c = "C"
local function a() -- Upvalue count: 3
    a = "B"
    b = "C"
    c = "D"
end

local function b() -- Upvalue Count: 2
    a = "E"
    b = "F"
end

a() -- Set variables
local original = closures.hookfunction(a, b) -- If this call fails, consider implicitly wrapping `b` in a newlclosure!

a()
assert(a == "E" and b == "F" and c == "D") -- If this errors, the upvalues are improperly set, and this could lead to a crash!
assert()

Example testing hooking pairs:

local upref = false
local dummyFunc = function(f)
    upref = f == "Hello"
end

local old; old = closures.hookfunction(coroutine.resume, dummyFunc) -- Hook C -> L. This test also checks that hookfunction can bypass upvalue limits.
coroutine.resume("Hello")

if not upref then
    error("hookfunction", "Failed to hook C -> L Closures")
end

upref = false
closures.hookfunction(coroutine.resume, old)
pcall(coroutine.resume, "Hello")

Errors

TypeDescription
Too many upvalues!The function to hook has more upvalues than the function you want to hook it with. (This error may not show on some implementations.)
This function cannot be hooked from LuauThe function has been marked as unhookable by `protectfunction` or by native executor code.

restorefunction

closures.restorefunction(
restoreWhat(T...) → U...--

The function that should be restored.

) → ()

Restores any hooks done to restoreWhat, regardless of the hook type (i.e., C closure hook, Luau closure hook, ...) and of how many hooks were done to restoreWhat.

Vulnerability

This function is vulnerable to the savedpc vulnerability, refer to hookfunction's documentation on how to fix this vulnerability!

Errors

TypeDescription
Function is not hookedThe function is not hooked, and thus cannot be restored. Use `ishooked` before calling this function!

loadstring

closures.loadstring(
luauCodestring,--

The code to compile and load.

chunkNamestring?--

The name of the chunk to load.

) → (
((T...) → U...) | nil,--

The loaded function, if loading succeeded.

string?--

The error message, if loading failed.

)

Compiles and loads the given luauCode. If chunkName is not provided, it must default to either: a pseudo-randomly generated string, or =loadstring.

Loadstring must compile the code with the following compile configuration: - Optimization Level 1 - Debug Level 1

The function must also mark the environment as 'unsafe' to disable environment optimizations as per Luau specifications.

Errors

TypeDescription
N/AThis function does not explicitly error, however it returns an error message if the code fails to load in the second return.

newcclosure

closures.newcclosure(
fn(T...) → U...,--

Function to wrap

debugNamestring?--

The name of the function, optional.

) → (T...) → U...

Wraps the given closure into a upvalue-less C closure.

Detections

The proxy emited by newlclosure and the called function MUST be automatically hidden from the callstack using sethiddenstack to prevent callstack attacks.

While executor functions are automatically hidden, if the closure given is not a hidden function (i.e., a roblox or game function) you must hide it accordingly.

Yielding

The emitted closure must yield. To implement this, check for lua_pcall on the Luau source!

newlclosure

closures.newlclosure(
fn(T...) → U...--

Function to wrap

) → (T...) → U...

Wraps the given closure into a upvalue-less L closure.

Obfuscators

Obfuscators like Luraph™ use the function environment to wrap closures and bypass upvalue limits. Make sure that the environment of the pushed wrapper proxies the environment of the original function using __index. This should fix issues relating to Luraph™ not working with this function.

WARNING

This function, depending on the implementation, may rely on function environments to work or in a closure map-backed proxy function (like in newcclosure). Beware of this when using the function.

Detections

The proxy emited by newlclosure and the called function MUST be automatically hidden from the callstack using sethiddenstack to prevent callstack attacks.

While executor functions are automatically hidden, if the closure given is not a hidden function (i.e., a roblox or game function) you must hide it accordingly.

Example Implementation (In Luau)

local function newlclosure<T..., U...>(f: (T...) -> U...): (T...) -> U...
    local env = getfenv(f) -- Get environment (env of f gets deoptimized)
    local x = setmetatable({
        __F = f, -- Store function on environment (prevents upreference)
    }, {
        __index = env, -- proxy original fenv for obfuscator support
        __newindex = env, -- proxy original fenv for obfuscator support
    })

    local nf = function(...)
        return __F(...)  -- proxy call
    end
    setfenv(nf, x) -- set func env (env of nf gets deoptimized)
    return nf
end

wrapclosure

closures.wrapclosure(
fn(T...) → U...--

Function to wrap

) → (T...) → U...--

A copy of the function, without upvalues.

Wraps the given closure into an upvalue-less version of itself.

INFO

This function is a proxy to the correct implementation of newcclosure/ newlclosure.

Examples:

local b = "b"
local function a() -- Upvalue Count: 1 ('b')
    b = "a"
end

assert(#debug.getupvalues(a) == 1, "debug.getupvalues failure") -- Validate the behaviour of debug.getupvalues.
assert(#debug.getupvalues(closures.wrapclosure(a)) == 0, "wrapped closure has upreferences/upvalues")
assert(islclosure(a) == iscclosure(closures.wrapclosure(a)), "closure type mismatch")

-- If this script errors, your wrapclosure may not be working properly.

isexecutorclosure

closures.isexecutorclosure(
fn(T...) → U...--

The function to check.

) → boolean--

Whether the function is an executor closure.

Checks if the given function is an executor closure.

Implementation

The function may use any method, as long as it returns true for executor Luau and C functions, as well as hooked functions, and false for any non-executor functions. This means that functions retrieved from loadstring or getscriptclosure will also return true.

clonefunction

closures.clonefunction(
fn(T...) → U...--

The function to clone.

) → (T...) → U...--

A clone of the function.

Clones the given function, providing a unique copy of it that is completely unrelated to fn.

Obfuscator Support

You must note that a cloned Luau function must have the SAME environment as the original function. If you do not provide an environment and an obfuscator depends on it for upvalues, you may face script errors.

This has been documented behaviour on scripts obfuscated by obfuscators such as Luraph, where newlclosure and/or clonefunction do not properly set the environment of the cloned function, which causes script errors.

isfunctionprotected

closures.isfunctionprotected(
fn(T...) → U...--

The function to check.

) → boolean--

If true, the function is protected against hooking.

Returns if the given function is protected against hooks.

Whitelists

You can use this function if your executor abides by rSUNC to determine if the environment is proper for your whitelist.

ishooked

closures.ishooked(
fn(T...) → U...--

The function to check.

) → boolean--

Whether the function has been hooked.

Returns if the given function has been hooked.

Whitelists

You can use this function if your executor abides by rSUNC to determine if the environment is proper for your whitelist.

protectfunction

closures.protectfunction(
fn(T...) → U...--

The function to make unhookable and remove all hooks of.

) → ()

Makes a function unable to be hooked. This function automatically reverts ANY hooks placed on the function if there are any set.

comparefunction

closures.comparefunction(
fn1(T...) → U...,--

The first function to compare.

fn2(T...) → U...--

The second function to compare.

) → boolean--

Whether the two functions point to the same code.

Returns true if the function points to the same code. The function should return false if the function type results in a mis-match (i.e., fn1 is a Luau closure and fn2 is a C closure).

newcclosures/ newlclosure comparison

For newcclosures and newlclosures, the result must be derived from the 'deepest' closure it wraps.

For example, if fn1 is a newcclosure that wraps a newlclosure that wraps a luau function, and fn2 is a Luau function, you must search to the deepest call fn1 can do and check if it is fn2. The same should happen if fn2 is the nested one and fn1 is a luau function.

This function does not check if the function code is the same, only if the two functions point to the same 'Prototype' or 'C function'.

Examples

print"comparefunction test"
local x = function() end
print(comparefunction(function() end, function() end)) -- false
print(comparefunction(x, x)) -- true
print(comparefunction(x, clonefunction(x))) -- true
print(comparefunction(x, newcclosure(x))) -- true
print(comparefunction(x, newlclosure(x))) -- true
print(comparefunction(newcclosure(x), x)) -- true
print(comparefunction(newlclosure(x), x)) -- true
print(comparefunction(print, newcclosure(print))) -- true
print(comparefunction(print, newlclosure(print))) -- true
print(comparefunction(print, newcclosure(warn))) -- false
print(comparefunction(print, newlclosure(warn))) -- false
print(comparefunction(newcclosure(print), newcclosure(print))) -- true
print(comparefunction(
    newcclosure(
        newlclosure(
            newcclosure(
                print
            )
        )
    ),
    newlclosure(
        newcclosure(
            newlclosure(
                print
            )
        )
    ))
) -- true
print"comparefunction test end"

iswrappedclosure

closures.iswrappedclosure(
fn(T...) → U...--

The function to check.

) → boolean--

Whether the function has been wrapped using a wrapper function, such as newlclosure, newcclosure or wrapclosure.

Returns if the given function is a wrapper function.

sethiddenstack

closures.sethiddenstack(
fnOrLevelnumber | (T...) → U...,--

The function or stack level to hide.

hiddenboolean--

If true, the function will be hidden from the callstack, else it will be unhidden.

) → ()

Hides a function from functions which expose the callstack to users (i.e., getfenv, setfenv, debug.info and debug.traceback).

Implementation

You must hide the Proto and the backing C function, not the actual Closure object. This is because the closure can be cloned, and then it will appear on the callstack!

INFO

All executor functions must be hidden from the callstack by default, and this cannot change regardless of the users' desire.

ishiddenstack

closures.ishiddenstack(
fnOrLevelnumber | (T...) → U...--

The function or stack level to check.

) → boolean--

If true, the function is hidden from the callstack.

Returns if the given function or stack level is hidden from functions which expose the callstack to users.

INFO

All executor functions must be hidden from the callstack by default, and this cannot change regardless of the users' desire.

trampoline_call

closures.trampoline_call(
fn(T...) → U...,--

The function to call with the fake stack and new thread.

stackLevels{CallStack},--

An array of callstacks, the first item will be the beginning of the callstack.

threadInitializationInformationThreadInitializationInformation,--

The thread initialization information to use.

...K...--

Additional arguments to pass to the function.

) → (
boolean,--

Whether the function was successfully called.

J...--

The return of the function

)

Creates a new thread and sets up a fake call stack for it, then calls the given function.

INFO

If on the stackLevels, a CallStack's currentline is not given (i.e., nil), the beginning of the function must be used by default.

Show raw api
{
    "functions": [
        {
            "name": "getfunctionhash",
            "desc": "Provides a hash with a digest size of 48 bytes (384 bits) in hex format for the given luau function\n\n\n\n:::info Implementation\nYou must use the instructions and constants of the given function to obtain the hash.\n:::\n\n:::danger Compiler\nIf your function is outputting different hashes every time its called, then you may have forgotten to account for Compiler settings. We recommend you read [this post](/docs/CompilerCompliance) to know to resolve this issue and make your Luau compiler rSUNC compliant.\n:::\n\n:::danger newcclosures/ newlclosure hashing\nFor newcclosures and newlclosures, the result of the hash must be come from the 'deepest' closure it wraps.\n\nIf such a closure is a C closure, then the function must error.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to hash.",
                    "lua_type": "(T...) -> U..."
                },
                {
                    "name": "hashType",
                    "desc": "The hash type, defaults to SHA384.",
                    "lua_type": "\"SHA384\" | \"BLAKE2B\" | \"BLAKE3\""
                }
            ],
            "returns": [
                {
                    "desc": "The hex representation of the selected hashType hash of the function.",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "the closure must resolve to a Luau closure",
                    "desc": "A wrapper function was provided, but when resolving its root closure, it didn't point to a luau closure."
                },
                {
                    "lua_type": "function must be a Luau closure.",
                    "desc": "The first parameter was a C function instead of a Luau function."
                },
                {
                    "lua_type": "Unsupported hash type.",
                    "desc": "The given hash type is not supported by the executor."
                }
            ],
            "source": {
                "line": 37,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "hookfunction",
            "desc": "Hooks the given function with the desired hook. This function supports hooking all kinds of closures, from Lua to C, C to Lua, etc.\n\n\n:::info Implementations\nSome implementations of this function may bypass upvalue limits for you. If this is not the case, wrap `hook` in `newcclosure`/`newlclosure` before calling `hookfunction`.\n:::\n\n:::tip Restoring Hooked Functions\nYou can restore functions hooked with `hookfunction` using `restorefunction`, in case `restorefunction` is not available you may unhook by calling `hookfunction` with the first function being the hooked function, and the second being the original one.\n:::\n\n:::danger Proto and savedpc\nWhen a proto is hooked and it is on a callstack of any thread you must emit a clone of it, and set the func parameter of the callstack to the clone.\n\nThis will prevent a vulnerability where, upon returning from a yield or C code, the savedpc which was set with the original proto, attempts to resume execution in the new proto.\n\nThis causes:\n    - Crashes in the form of Segmentation Faults\n    - Line number error to be displayed improperly.\n\nAmong other things, you must either emit a clone of the proto or replace the callstack of that thread with the hooked one and perform all proper validation for it.\n\nExample of line number being dislayed improperly if the function implementation is vulnerable: https://i.imgur.com/5sSfnut.png _RbxStu V4 has fixed this issue already!_\n:::\n\n## Vulnerability test code:\n```lua\nlocal bFinishedExecution = false\nlocal bExecuted = false\nlocal a \n\nlocal function b()\n    print\"b babyyy\"\n    print\"b baby\"\n    print\"b babyyy\"\n    restorefunction(a) -- may be affected as well, and you must apply similar fixes!\n    warn(\"im b\")\n    bExecuted = true\nend\n\na = function()\n    local x = hookfunction(a, b)\n    task.wait()\n    print(\"I'm a\")\n    bFinishedExecution = true\n    return x\nend\n\nlocal s, e = pcall(a)\nassert(s, \"savedpc [1]\")\nassert((bFinishedExecution and not bExecuted) or (not bFinishedExecution and bExecuted), \"savedpc [2]\")\nprint(\"no issues most likely\")\n\n--[[\n    When hooking and suspending, the current instruction is saved into the threads' CallInfo->savedpc to resume. But as we have changed the Proto object, the address and offset of this savedpc, is NO LONGER VALID!\n    Attempting to do anything with it would likely yield us either: a Luau C error with line number of the error in a completely invalid line, undefined behaviour and inexplicable crashes.\n\n    This is a potential ACE that MUST be fixed and handled properly.\n]]\n```\n\n## Examples:\n\n### Testing hookfunction and restorefunction:\n```lua\nlocal function a()\n    return \"baa\"\nend\nlocal function b()\n    return \"caa\"\nend\n\nlocal aReturn = a()\nlocal bReturn = b()\nlocal hooked = closures.hookfunction(a, b)\nlocal hookedReturn = hooked()\nassert(hookedReturn == aReturn)\nassert(a() == b() and a() == bReturn)\nassert(hooked ~= a and b ~= a)\nclosures.restorefunction(hooked, a) -- Restore a again.\nassert(a() == aReturn and a() ~= hookedReturn)\n\n-- If this script errors, your hookfunction may not be working properly.\n```\n\n### Example testing upvalue limits:\n```lua\nlocal a = \"A\"\nlocal b = \"B\"\nlocal c = \"C\"\nlocal function a() -- Upvalue count: 3\n    a = \"B\"\n    b = \"C\"\n    c = \"D\"\nend\n\nlocal function b() -- Upvalue Count: 2\n    a = \"E\"\n    b = \"F\"\nend\n\na() -- Set variables\nlocal original = closures.hookfunction(a, b) -- If this call fails, consider implicitly wrapping `b` in a newlclosure!\n\na()\nassert(a == \"E\" and b == \"F\" and c == \"D\") -- If this errors, the upvalues are improperly set, and this could lead to a crash!\nassert()\n```\n\n### Example testing hooking pairs:\n```lua\nlocal upref = false\nlocal dummyFunc = function(f)\n    upref = f == \"Hello\"\nend\n\nlocal old; old = closures.hookfunction(coroutine.resume, dummyFunc) -- Hook C -> L. This test also checks that hookfunction can bypass upvalue limits.\ncoroutine.resume(\"Hello\")\n\nif not upref then\n    error(\"hookfunction\", \"Failed to hook C -> L Closures\")\nend\n\nupref = false\nclosures.hookfunction(coroutine.resume, old)\npcall(coroutine.resume, \"Hello\")\n```",
            "params": [
                {
                    "name": "fn",
                    "desc": "Function to hook.",
                    "lua_type": "(T...) -> U..."
                },
                {
                    "name": "hook",
                    "desc": "The hook.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "The old/original function.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "Too many upvalues!",
                    "desc": "The function to hook has more upvalues than the function you want to hook it with. (This error may not show on some implementations.)"
                },
                {
                    "lua_type": "This function cannot be hooked from Luau",
                    "desc": "The function has been marked as unhookable by `protectfunction` or by native executor code."
                }
            ],
            "source": {
                "line": 176,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "restorefunction",
            "desc": "Restores any hooks done to `restoreWhat`, regardless of the hook type (i.e., C closure hook, Luau closure hook, ...) and of how many hooks were done to `restoreWhat`.\n\n\n:::danger Vulnerability\nThis function is vulnerable to the `savedpc` vulnerability, refer to [`hookfunction`](/api/closures#hookfunction)'s documentation on how to fix this vulnerability! \n:::",
            "params": [
                {
                    "name": "restoreWhat",
                    "desc": "The function that should be restored.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "Function is not hooked",
                    "desc": "The function is not hooked, and thus cannot be restored. Use `ishooked` before calling this function!"
                }
            ],
            "source": {
                "line": 190,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "loadstring",
            "desc": "Compiles and loads the given `luauCode`. If `chunkName` is not provided, it must default to either: a pseudo-randomly generated string, or `=loadstring`.\n\nLoadstring must compile the code with the following compile configuration:\n    - Optimization Level 1\n    - Debug Level 1\n\nThe function must also mark the environment as 'unsafe' to disable environment optimizations as per Luau specifications. ",
            "params": [
                {
                    "name": "luauCode",
                    "desc": "The code to compile and load.",
                    "lua_type": "string"
                },
                {
                    "name": "chunkName",
                    "desc": "The name of the chunk to load.",
                    "lua_type": "string?"
                }
            ],
            "returns": [
                {
                    "desc": "The loaded function, if loading succeeded.",
                    "lua_type": "((T...) -> U...) | nil"
                },
                {
                    "desc": "The error message, if loading failed.",
                    "lua_type": "string?"
                }
            ],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "N/A",
                    "desc": "This function does not explicitly error, however it returns an error message if the code fails to load in the second return."
                }
            ],
            "source": {
                "line": 209,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "newcclosure",
            "desc": "Wraps the given closure into a upvalue-less C closure.\n\n\n:::danger Detections\nThe proxy emited by newlclosure and the called function MUST be automatically hidden from the callstack using `sethiddenstack` to prevent callstack attacks.\n\nWhile executor functions are automatically hidden, if the closure given is not a hidden function (i.e., a roblox or game function) you must hide it accordingly.\n:::\n\n:::warning Yielding\nThe emitted closure must yield. To implement this, check for lua_pcall on the Luau source!\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "Function to wrap",
                    "lua_type": "(T...) -> U..."
                },
                {
                    "name": "debugName",
                    "desc": "The name of the function, optional.",
                    "lua_type": "string?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "function_type": "static",
            "source": {
                "line": 231,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "newlclosure",
            "desc": "Wraps the given closure into a upvalue-less L closure.\n\n\n:::info Obfuscators\nObfuscators like Luraph™ use the function environment to wrap closures and bypass upvalue limits. Make sure that the environment of the pushed wrapper proxies the environment of the original function using `__index`. This should fix issues relating to Luraph™ not working with this function.\n:::\n\n:::warning\nThis function, depending on the implementation, may rely on function environments to work or in a closure map-backed proxy function (like in newcclosure). Beware of this when using the function.\n:::\n\n:::danger Detections\nThe proxy emited by newlclosure and the called function MUST be automatically hidden from the callstack using `sethiddenstack` to prevent callstack attacks.\n    \nWhile executor functions are automatically hidden, if the closure given is not a hidden function (i.e., a roblox or game function) you must hide it accordingly.\n:::\n\n#### Example Implementation (In Luau)\n```lua\nlocal function newlclosure<T..., U...>(f: (T...) -> U...): (T...) -> U...\n    local env = getfenv(f) -- Get environment (env of f gets deoptimized)\n    local x = setmetatable({\n        __F = f, -- Store function on environment (prevents upreference)\n    }, {\n        __index = env, -- proxy original fenv for obfuscator support\n        __newindex = env, -- proxy original fenv for obfuscator support\n    })\n\n    local nf = function(...)\n        return __F(...)  -- proxy call\n    end\n    setfenv(nf, x) -- set func env (env of nf gets deoptimized)\n    return nf\nend\n```",
            "params": [
                {
                    "name": "fn",
                    "desc": "Function to wrap",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "function_type": "static",
            "source": {
                "line": 275,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "wrapclosure",
            "desc": "Wraps the given closure into an upvalue-less version of itself.\n\n\n:::info \nThis function is a proxy to the correct implementation of `newcclosure`/ `newlclosure`.\n:::\n\n### Examples:\n\n```lua\nlocal b = \"b\"\nlocal function a() -- Upvalue Count: 1 ('b')\n    b = \"a\"\nend\n\nassert(#debug.getupvalues(a) == 1, \"debug.getupvalues failure\") -- Validate the behaviour of debug.getupvalues.\nassert(#debug.getupvalues(closures.wrapclosure(a)) == 0, \"wrapped closure has upreferences/upvalues\")\nassert(islclosure(a) == iscclosure(closures.wrapclosure(a)), \"closure type mismatch\")\n\n-- If this script errors, your wrapclosure may not be working properly.\n```",
            "params": [
                {
                    "name": "fn",
                    "desc": "Function to wrap",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "A copy of the function, without upvalues.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "function_type": "static",
            "source": {
                "line": 305,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "isexecutorclosure",
            "desc": "Checks if the given function is an executor closure.\n\n\n:::info Implementation\nThe function may use any method, as long as it returns true for executor Luau and C functions, as well as hooked functions, and false for any non-executor functions. This means that functions retrieved from loadstring or getscriptclosure will also return true.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to check.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "Whether the function is an executor closure.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 320,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "clonefunction",
            "desc": "Clones the given function, providing a unique copy of it that is completely unrelated to `fn`.\n\n\n:::danger Obfuscator Support\nYou must note that a cloned Luau function must have the SAME environment as the original function. If you do not provide an environment and an obfuscator depends on it for upvalues, you may face script errors.\n    \nThis has been documented behaviour on scripts obfuscated by obfuscators such as Luraph, where `newlclosure` and/or `clonefunction` do not properly set the environment of the cloned function, which causes script errors.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to clone.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "A clone of the function.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "function_type": "static",
            "source": {
                "line": 337,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "isfunctionprotected",
            "desc": "Returns if the given function is protected against hooks.\n\n\n:::tip Whitelists\nYou can use this function if your executor abides by rSUNC to determine if the environment is proper for your whitelist.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to check.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "If true, the function is protected against hooking.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 352,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "ishooked",
            "desc": "Returns if the given function has been hooked.\n\n\n:::tip Whitelists\nYou can use this function if your executor abides by rSUNC to determine if the environment is proper for your whitelist.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to check.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "Whether the function has been hooked.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 367,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "protectfunction",
            "desc": "Makes a function unable to be hooked. This function automatically reverts ANY hooks placed on the function if there are any set.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to make unhookable and remove all hooks of.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 378,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "comparefunction",
            "desc": "Returns true if the function points to the same code. The function should return false if the function type results in a mis-match (i.e., `fn1` is a Luau closure and `fn2` is a C closure).\n\n\n:::danger newcclosures/ newlclosure comparison\nFor newcclosures and newlclosures, the result must be derived from the 'deepest' closure it wraps.\n\nFor example, if `fn1` is a newcclosure that wraps a newlclosure that wraps a luau function, and `fn2` is a Luau function, you must search to the deepest call fn1 can do and check if it is `fn2`.\nThe same should happen if `fn2` is the nested one and `fn1` is a luau function.\n:::\n\nThis function does not check if the function code is the same, only if the two functions point to the same 'Prototype' or 'C function'.\n\n### Examples\n\n```lua\nprint\"comparefunction test\"\nlocal x = function() end\nprint(comparefunction(function() end, function() end)) -- false\nprint(comparefunction(x, x)) -- true\nprint(comparefunction(x, clonefunction(x))) -- true\nprint(comparefunction(x, newcclosure(x))) -- true\nprint(comparefunction(x, newlclosure(x))) -- true\nprint(comparefunction(newcclosure(x), x)) -- true\nprint(comparefunction(newlclosure(x), x)) -- true\nprint(comparefunction(print, newcclosure(print))) -- true\nprint(comparefunction(print, newlclosure(print))) -- true\nprint(comparefunction(print, newcclosure(warn))) -- false\nprint(comparefunction(print, newlclosure(warn))) -- false\nprint(comparefunction(newcclosure(print), newcclosure(print))) -- true\nprint(comparefunction(\n    newcclosure(\n        newlclosure(\n            newcclosure(\n                print\n            )\n        )\n    ),\n    newlclosure(\n        newcclosure(\n            newlclosure(\n                print\n            )\n        )\n    ))\n) -- true\nprint\"comparefunction test end\"\n```",
            "params": [
                {
                    "name": "fn1",
                    "desc": "The first function to compare.",
                    "lua_type": "(T...) -> U..."
                },
                {
                    "name": "fn2",
                    "desc": "The second function to compare.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "Whether the two functions point to the same code.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 433,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "iswrappedclosure",
            "desc": "Returns if the given function is a wrapper function.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to check.",
                    "lua_type": "(T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "Whether the function has been wrapped using a wrapper function, such as newlclosure, newcclosure or wrapclosure.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 444,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "sethiddenstack",
            "desc": "Hides a function from functions which expose the callstack to users (i.e., `getfenv`, `setfenv`, `debug.info` and `debug.traceback`).\n\n\n:::danger Implementation\nYou must hide the Proto and the backing C function, _not_ the actual Closure object. This is because the closure can be cloned, and then it will appear on the callstack!\n:::\n\n:::info \nAll executor functions must be hidden from the callstack by default, and this cannot change regardless of the users' desire.\n:::",
            "params": [
                {
                    "name": "fnOrLevel",
                    "desc": "The function or stack level to hide.",
                    "lua_type": "number | (T...) -> U..."
                },
                {
                    "name": "hidden",
                    "desc": "If true, the function will be hidden from the callstack, else it will be unhidden.",
                    "lua_type": "boolean"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 463,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "ishiddenstack",
            "desc": "Returns if the given function or stack level is hidden from functions which expose the callstack to users.\n\n\n:::info \nAll executor functions must be hidden from the callstack by default, and this cannot change regardless of the users' desire.\n:::",
            "params": [
                {
                    "name": "fnOrLevel",
                    "desc": "The function or stack level to check.",
                    "lua_type": "number | (T...) -> U..."
                }
            ],
            "returns": [
                {
                    "desc": "If true, the function is hidden from the callstack.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 476,
                "path": "impl/Libraries/closures.luau"
            }
        },
        {
            "name": "trampoline_call",
            "desc": "Creates a new thread and sets up a fake call stack for it, then calls the given function.\n\n\n:::info\nIf on the `stackLevels`, a `CallStack`'s `currentline` is not given (i.e., `nil`), the beginning of the function must be used by default.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to call with the fake stack and new thread.",
                    "lua_type": "(T...) -> U..."
                },
                {
                    "name": "stackLevels",
                    "desc": "An array of callstacks, the first item will be the beginning of the callstack.",
                    "lua_type": "{ CallStack }"
                },
                {
                    "name": "threadInitializationInformation",
                    "desc": "The thread initialization information to use.",
                    "lua_type": "ThreadInitializationInformation"
                },
                {
                    "name": "...",
                    "desc": "Additional arguments to pass to the function.",
                    "lua_type": "K..."
                }
            ],
            "returns": [
                {
                    "desc": "Whether the function was successfully called.",
                    "lua_type": "boolean"
                },
                {
                    "desc": "The return of the function",
                    "lua_type": "J..."
                }
            ],
            "function_type": "static",
            "source": {
                "line": 498,
                "path": "impl/Libraries/closures.luau"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "closures",
    "desc": "Contains functions which interact and modify closures and their behaviour.",
    "tags": [
        "Library"
    ],
    "source": {
        "line": 8,
        "path": "impl/Libraries/closures.luau"
    }
}