PrevUpHomeNext

CppPcall

Primer contains a handy utility called cpp_pcall which can be used to execute any C++ call with any arguments inside of a lua protected context.

This is essentially an extension of the cpcall idiom described on the lua mailing lists, except using C++ generic programming techniques.

For reference, see this description and example of lua_cpcall usage.

int operate(lua_State * L, std::string & s, int x, int y) {
    std::string msg = "Calling " + s + "\n";  // can raise exception; must be destroyed
    cout << msg;
    // caution: this code by raise exceptions but not longjump.
    struct C {
        static int call(lua_State * L) {
            // caution: this code may longjump but not raise exceptions.

            C * p = static_cast<C*>(lua_touserdata(L, 1));
            assert(lua_checkstack(L, 4));
            lua_getglobal("add"); // can longjump
            assert(lua_isfunction(L, -1));
            lua_pushstring(L, s); // can longjump
            lua_pushnumber(L, p->x);
            lua_pushnumber(L, p->y);
            lua_call(L, 3, 1);    // can longjump
            p->z = lua_tonumber(L, -1); assert(lua_isnumber(L, -1));
            return 0;
        }
        const char * s; int x; int y; int z;
    } p = {s.c_str(), x, y, 0};
    int res = lua_cpcall(L, C::call, &p); // never longjumps
    if (res != 0) {
        handle_error(L);  // do something with the error; can raise exception
        //note: we let handle_error do lua_pop(L, 1);
    }
    return p.z;
}

In summary, the idea is to create a local struct that will hold pointers to the call parameters, then do something with them in a lua callback, then, push that callback and a pointer to the structure to pcall.

cpp_pcall accomplishes the same but without the boiler-plate. In addition, cpp_pcall can work not only with a function pointer, but any C++ function object or lambda function, which often makes this whole idiom much nicer.

namespace primer {

1template <int narg = 0, typename F, typename... Args>
expected<void> cpp_pcall(lua_State * L, F && f, Args &&... args) noexcept;
}

1

Takes a callable and some arguments, performs the call in a protected context which captures lua errors.

Usually, f needs a lua State * also as one of the arguments, to do whatever it will do. It might or might not be the same lua_State *, it could be operating on a thread.

The first parameter to cpp_pcall should be the main thread, and it should be related to whatever threads f is operating on, or the error capture won't work.

The optional template parameter narg signals how many values on the top of L's stack should be consumed as arguments to the pcall. By default, none are.

The callable itself must not throw a C++ exception, or terminate will be called.

A related function, mem_pcall, is used internally by the Primer to capture lua memory errors as appropriate according to the value of PRIMER_NO_MEMORY_FAILURE.

Sometimes, we perform operations which can raise lua errors, but only of the memory variety. In this case, we call mem_pcall instead of cpp_pcall, which is resolves to either a cpp_pcall or a simple invocation of the callable, depending on the PRIMER_NO_MEMORY_FAILURE define. This, together with PRIMER_TRY_BAD_ALLOC / PRIMER_CATCH_BAD_ALLOC, is the total effect of the PRIMER_NO_MEMORY_FAILURE switch. mem_pcall should not be used with calls that can throw an exception. It also should not be used with calls that can raise lua errors of the non-memory variety, since when PRIMER_NO_MEMORY_FAILURE is defined the protection is stripped out. If you need full protection then use cpp_pcall directly.

template <int narg = 0, typename F, typename... Args>
expected<void>
mem_pcall(lua_State * L, F && f, Args &&... args) noexcept {
#ifdef PRIMER_NO_MEMORY_FAILURE
  static_cast<void>(L);
  std::forward<F>(f)(std::forward<Args>(args)...);
  return {};
#else
  return cpp_pcall<narg>(L, std::forward<F>(f), std::forward<Args>(args)...);
#endif
}

For instance, here's an example from the implementation of bound_function. protected_call is a member function of bound function which is used to implement all of its forwarding facing functions.

// Calls the bound_function in a protected context. This is no fail.
template <typename return_type, typename... Args>
expected<return_type> protected_call(Args &&... args) const noexcept {
  expected<return_type> result{primer::error::cant_lock_vm()};
  if (lua_State * L = ref_.lock()) {
    if (auto stack_check = detail::check_stack_push_each<int, Args...>(L)) {
      auto ok = mem_pcall(L, [&]() {
        ref_.push(L);
        primer::push_each(L, std::forward<Args>(args)...);
        detail::fcn_call(result, L, sizeof...(args));
      });

      if (!ok) { result = std::move(ok.err()); }
    } else {
      result = std::move(stack_check.err());
    }
  }
  return result;
}

In this code, the lambda contains calls that could potentially cause a lua memory failure, when pushing objects and also popping return values off of the stack. mem_pcall allows us to catch all such errors, and when PRIMER_NO_MEMORY_FAILURE is defined, the mem_pcall and the lambda are inlined, producing a direct call with no overhead.

Implementation Details

For the curious, cpp_pcall's implementation looks like this:

namespace detail {

template <typename T>
int
lambda_upvalue_dispatch(lua_State * L) {
  T * t = static_cast<T *>(lua_touserdata(L, lua_upvalueindex(1)));
  (*t)();
  return lua_gettop(L);
}

} // end namespace detail

template <int narg, typename F, typename... Args>
expected<void>
cpp_pcall(lua_State * L, F && f, Args &&... args) noexcept {
  expected<void> result;

  auto lambda = [&]() { (std::forward<F>(f))(std::forward<Args>(args)...); };
  lua_pushlightuserdata(L, static_cast<void *>(&lambda));
  lua_pushcclosure(L, &detail::lambda_upvalue_dispatch<decltype(lambda)>, 1);

  if (narg) { lua_insert(L, -1 - narg); }

  int code; // pcall_helper installs a custom error handler
  std::tie(code, std::ignore) = detail::pcall_helper(L, narg, LUA_MULTRET);

  if (code != LUA_OK) { result = pop_error(L, code); }
  return result;
}

PrevUpHomeNext