Skip to content

Lua State

Quahu edited this page Jun 4, 2024 · 5 revisions

Creating a Lua state

To create a Lua state, you can use any of the constructor overloads of the Lua type.

using (var lua = new Lua())
{
    // do stuff
}

Important

Lua is disposable; disposing a Lua instance closes the underlying Lua state and frees any allocated memory. You must dispose of unused Lua states to free the allocated memory.

Note

In future examples, the wiki might omit the instantiation of Lua and simply use a lua variable which you're meant to replace with your instance.

Running Lua Code

Lua code is run by loading a chunk and then calling it. See Lua manual.

Lua defines a few methods for running Lua code:

  • Execute() - loads the specified Lua code and immediately calls it, without returning any values
  • Evaluate() - loads the specified Lua code and immediately calls it, returning the values the chunk may have returned
    • Evaluate<T>() can be used for a single value return
  • Load() - loads the specified Lua code and returns it as a LuaFunction, allowing for manual execution at a later point

Examples

lua.Execute("x = 2");

var result = lua.Evaluate<int>("return x + 2");
Console.WriteLine(result); // 4
Load() is a bit more complex, as it returns a LuaFunction which we have to call manually.
using (var chunk = lua.Load("x = x + 1 return x"))
{
    for (var i = 0; i < 3; i++)
    {
        using (var results = chunk.Call())
        {
            result = results.First.GetValue<int>();
            Console.WriteLine(result);
        }
    }
}

Global Variables

To pass variables between Lua code and .NET, global variables can be used. See Lua manual.

Lua exposes multiple ways to get and set global variables within Lua:

  • TryGetGlobal<T>() and GetGlobal<T>() - retrieves the value of the global variable with the specified name
  • SetGlobal<T>() - sets the value of the global variable with the specified name
  • Globals property - returns a LuaTable with the global variables

Examples

lua.SetGlobal("x", 42);

var x = lua.GetGlobal<int>("x");
Console.WriteLine(x); // 42

Lua.Globals allows for more operations on the global variables:

var hasX = lua.Globals.ContainsKey("x");
Console.WriteLine(hasX); // True

var globalCount = lua.Globals.Count();
Console.WriteLine(globalCount); // 1

Libraries

Lua offers a wide range of useful standard libraries. See Lua manual.

Laylua exposes these standard libraries through the LuaLibraries.Standard nested static type.
For example, the mathematical functions library can be accessed via LuaLibraries.Standard.Math.

Laylua utilizes the ILuaLibrary interface to handle both standard libraries and custom libraries. This interface enables the opening and closing functionalities for the specified Lua instance, allowing you to manage the accessibility of libraries dynamically.

Examples

Opening all standard libraries:

lua.OpenStandardLibraries();

Getting the value of $\pi$ from Lua:

lua.OpenLibrary(LuaLibraries.Standard.Math);

var pi = lua.Evaluate<double>("return math.pi");
Console.WriteLine(pi); // 3.141592653589793

Exceptions

When it comes to Lua and its related entities, they follow the standard usage of exceptions such as ArgumentException or InvalidOperationException. However, there are specific exception types designed for handling failures in Lua interactions.

  • LuaException serves as the base exception type that is thrown when an error occurs during Lua interaction.

  • LuaPanicException is the exception type used when an error happens in an unprotected environment and Lua triggers the panic handler. This type of exception is commonly associated with user code issues, although not always, and by default, it causes Lua to terminate the application. Laylua uses a customized panic handler that prevents the application from terminating and instead raises the exception. This behavior applies to both Windows and Unix systems.

Examples

lua.Execute("return x.y"); // LuaException: [string "?"]:1: attempt to index a nil value (global 'x')

Sandboxing a Lua state

Sandboxing in Laylua is very straightforward. This is mainly because when you create a Lua state the library does not inject anything into it. You have the freedom to selectively load specific libraries of your preference, meaning Lua only has access to whatever you let it have access to.

Additionally, Laylua comes with built-in features for memory allocation and instruction count limiting. This is done using custom allocators and hooks.

Warning

While there are various sandboxing options available in Lua and Laylua, it is important to note that unless the running code is isolated within a sandboxed virtual environment, you cannot reliably ensure that malicious code will not bypass these measures and potentially harm the host machine.

Allocator

The Laylua.Moon.LuaAllocator type is an unsafe construct that can be used to control if and how memory is allocated by Lua. It essentially represents a callback Lua will invoke whenever it requires a chunk of memory to be allocated, reallocated, or deallocated.

Note

An allocator can be specified when instantiating a Lua state and cannot be changed to a different allocator afterwards.

Laylua comes with the built-in Laylua.Moon.NativeMemoryLuaAllocator type which functions just like the default native memory allocator in Lua, but has the following additional features:

  • maximum bytes allocated threshold
  • allocation metrics
  • allocation events

Example

Creating a Lua state with memory allocation limits.

nuint maxBytes = 8 * 1024; // 8KiB
var allocator = new NativeMemoryLuaAllocator(maxBytes);
using (var lua = new Lua(allocator))
{
    // LuaPanicException: 'not enough memory'
    using (var largeTable = lua.CreateTable(4096))
    { }
}

Hook

The Laylua.Moon.LuaHook type is an unsafe construct that can be used to intercept code execution at specific points determined by the set event mask. It essentially represents a callback Lua can invoke whenever the following happen:

  • function is called
  • function returns
  • new line of code is executed
  • given amount of instructions was executed

Tip

Instruction count limiting allows you to easily prevent, for example, infinite loops in Lua code.

Note

A hook can be set at any given time on the low-level Lua state. Only one hook can be set at a time.

Laylua comes with the following built-in hook implementations:

  • Laylua.Moon.MaxInstructionCountLuaHook which raises an error when the amount of instructions the Lua state is allowed to execute is exceeded
  • Laylua.Moon.CancellationTokenLuaHook which raises an error when cancellation is requested

Examples

using (var lua = new Lua())
{
    lua.State.Hook = new MaxInstructionCountLuaHook(500);

    lua.Execute("while true do end"); // LuaException: The maximum instruction count of 500 was exceeded by main code.
}
using (var cts = new CancellationTokenSource(50))
using (var lua = new Lua())
{
    lua.State.Hook = new CancellationTokenLuaHook(cts.Token);

    lua.Execute("while true do end"); // LuaException: The execution has been cancelled.
}