Files
ladybird/Meta/Lagom/Tools/LibJSGCVerifier
Matthew Olsson 76fa127cbf LibJSGCVerifier: Detect stack-allocated ref captures in lambdas
For example, consider the following code snippet:

    Vector<Function<void()>> m_callbacks;
    void add_callback(Function<void()> callback)
    {
    	m_callbacks.append(move(callback));
    }

    // Somewhere else...
    void do_something()
    {
    	int a = 10;
    	add_callback([&a] {
            dbgln("a is {}", a);
    	});
    } // Oops, "a" is now destroyed, but the callback in m_callbacks
      // has a reference to it!

We now statically detect the capture of "a" in the lambda above and flag
it as incorrect. Note that capturing the value implicitly with a capture
list of `[&]` would also be detected.

Of course, many functions that accept Function<...> don't store them
anywhere, instead immediately invoking them inside of the function. To
avoid a warning in this case, the parameter can be annotated with
NOESCAPE to indicate that capturing stack variables is fine:

    void do_something_now(NOESCAPE Function<...> callback)
    {
    	callback(...)
    }

Lastly, there are situations where the callback does generally escape,
but where the caller knows that it won't escape long enough to cause any
issues. For example, consider this fake example from LibWeb:

    void do_something()
    {
    	bool is_done = false;
    	HTML::queue_global_task([&] {
            do_some_work();
            is_done = true;
        });
    	HTML::main_thread_event_loop().spin_until([&] {
            return is_done;
        });
    }

In this case, we know that the lambda passed to queue_global_task will
be executed before the function returns, and will not persist
afterwards. To avoid this warning, annotate the type of the capture
with IGNORE_USE_IN_ESCAPING_LAMBDA:

    void do_something()
    {
   	IGNORE_USE_IN_ESCAPING_LAMBDA bool is_done = false;
    	// ...
    }
2024-04-09 09:10:44 +02:00
..

LibJSGCVerifier

This is a simple Clang tool to validate certain behavior relating to LibJS's GC. It currently validates two things:

  • For all types wrapped by GCPtr or NonnullGCPtr, that the wrapped type inherits from Cell
  • For all types not wrapped by GCPtr or NonnullGCPtr, that the wrapped type does not inherit from Cell (otherwise it should be wrapped).

This tool currently support being built with the Serenity Clang toolchain or the lagom Clang toolchain (in which case it won't be able to verify Serenity-only applications)

Building & Running with the Serenity toolchain

First build Serenity with the Clang toolchain for x86_64:

./Meta/serenity.sh build x86_64 Clang

Then build the tool with:

cmake -GNinja -B build
cmake --build build

Then run the tool with:

src/main.py -b <path to serenity>/Build

Building & Running with the Lagom toolchain

First build the Serenity lagom applications with:

./Meta/serenity.sh build lagom

Then build the tool with:

cmake -GNinja -DLAGOM_BUILD=ON -B build
cmake --build build

Then run the tool with:

src/main.py -l -b <path to serenity>/Build