Hot++

Rules, errors and traceback

Rules for all situations

Ownership and lifetime

Threads

These rules come from the comments in include/hot/runtime/loader.h.

Symbol export

A host that loads a module must export its symbols. The modules resolve the host functions and the runtime functions from the executable.

C++17 baseline

The generated code, include/hot and the runtime library need C++17 only. A C++17 host works. The standard of the scripts is the standard of the host (17, 20 or 23). A standard below 20 selects fmt for ${}. See CMake for the table.

Core copies

module_from_library makes a second, independent instance of the library for the loader (the “core”). It copies the file to the temp folder as hot-core-<pid>-<n>-<name> and removes the copy after dlclose. If the temp folder is not available, it uses the folder of the library. If the copy fails (cannot copy <path> for the core module: ...), the generation stays loaded and the loader prints [script] WARNING: no core module for ... to stderr.

Error reports

A script can throw NullReference, IndexOutOfRange, ArithmeticError or ScriptError (see Statements). All of them derive from hot::ScriptError, which derives from std::runtime_error. Include hot/runtime.h.

When the host calls a script function and the script does not catch a ScriptError, the runtime reports it. Then the call returns a default value (0, false, or nothing for void). The object stays alive. The next call works. A field keeps the values that the script wrote before the throw.

Set a handler to receive the report:

hot::set_script_error_handler([](const hot::ScriptErrorReport& r) {
    log("%s in %s: %s", r.type, r.class_name, r.message);
});

hot::set_script_error_handler({}) restores the default. hot::script_error_handler() gives the current handler. The handler runs on the thread of the call. These fields are valid only during the call of the handler:

Field Meaning
type hot::NullReference, hot::IndexOutOfRange, hot::ArithmeticError or hot::ScriptError.
message The text of what().
class_name The script class of the object, or ?.
self The shell of the object (const hot::ShellCore*), or null. The state member needs an object that is not optimized (hot::ShellBase).
traceback The script call stack of the throw point, or an empty text when the traceback is off. See “Traceback”.

Without a handler, the runtime prints one line to stderr: [script] ERROR: unhandled hot::IndexOutOfRange in Player: <message>. When the traceback is on, the lines of the traceback follow it.

An error reports only when it leaves the outermost script call. A script that calls native code, which calls a script override, can catch the error in the calling script.

Only a ScriptError stops at the boundary. Any other C++ exception that a script throws reaches the host. The host must catch it. If the return type has no default value (for example a reference), the runtime reports the error and throws it again.

A noexcept native virtual function cannot throw, so the runtime reports the error and must return a value. If the return type has no default value, the host gives that value with a specialization of hot::noexcept_result<R>. Without it, the exception leaves the noexcept function and ends the program.

#include "hot/runtime.h"
#include <string>

namespace hot
{
template <> struct noexcept_result<const std::string&>
{
    static const std::string& get()
    {
        static const std::string empty;
        return empty;
    }
};
} // namespace hot

The checks are a build-time choice. The CMake option HOT_RUNTIME_CHECKS (default ON) sets the macro HOT_RUNTIME_CHECKS. With OFF the macro is 0: the generated code has no checks, and an installed host sees the same setting. Each check costs one compare for a pointer access, a handle access or an index of a checked container (std::array, std::vector, a stub with checked_index). See CMake.

Traceback

The traceback is off by default. To turn it on, configure Hot++ with -DHOT_TRACEBACK=ON. The option sets the macro HOT_TRACEBACK to 1 for the runtime library and for the compile of the scripts. It works for .hot scripts only.

With the traceback on, each script member function records a frame on a thread-local stack. Each statement stores its script line in the frame. A hot::ScriptError copies the stack when the script throws it, because the unwind removes the frames. hot::ScriptError::traceback() gives the text. hot::ScriptErrorReport::traceback has the same text. Each frame is one line, and the innermost frame is first:

[script] ERROR: unhandled hot::NullReference in Player: null handle dereference
  at Player.Inner (player.hot:12)
  at Player.Outer (player.hot:17)
  at Player.Tick (player.hot:21)

The stack keeps the 64 innermost frames. A last line ` … N more` shows how many outer frames it dropped. A lambda records no frame and no line.

The cost is one store for each statement and one push and pop for each call. The generated code is the same when the option is OFF: the macros HOT_TRACE_FRAME and HOT_TRACE_LINE are empty. Build the host and the scripts with the same setting. A script without the macros gives an empty traceback.