Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sol_lua_check_access is not called for this when calling wrapped member functions #1329

Open
cschreib opened this issue Mar 25, 2022 · 2 comments

Comments

@cschreib
Copy link
Contributor

Issue #1074 introduced a new customization point to handle checked access. This is currently done only for "real" function arguments, and not for "implicit" argument like the this pointer when calling a member function. The following code illustrates the problem:

#include <sol/sol.hpp>
#include <memory>
#include <iostream>

// Create a user class
struct foo {
    int value;

    foo(int v) : value(v) {}
    ~foo() { value = 0; }

    void bar() { std::cout << value << std::endl; }
};

// Register customization points to sol to handle weak pointer
namespace sol {
    template<typename T>
    struct unique_usertype_traits<std::weak_ptr<T>> {
        static T* get(lua_State*, const std::weak_ptr<T>& ptr) noexcept {
            return ptr.lock().get();
        }

        static bool is_null(lua_State*, const std::weak_ptr<T>& ptr) noexcept {
            return ptr.expired();
        }
    };
}

template<typename T>
void sol_lua_check_access(sol::types<T>, lua_State* L, int index, sol::stack::record& tracking) {
    sol::optional<std::weak_ptr<T>&> maybe_checked =
        sol::stack::check_get<std::weak_ptr<T>&>(L, index, sol::no_panic, tracking);

    if (!maybe_checked.has_value()) {
        return;
    }

    if (maybe_checked->expired()) {
        // We want to throw if the pointer has expired
        throw std::runtime_error("object has been deleted");
    }
}

int main() {
    sol::state lua;

    // Register our user type
    auto foo_type = lua.new_usertype<foo>("Foo");
    foo_type.set_function("bar", &foo::bar);

    // Create an object and give Lua an observer to it
    std::shared_ptr<foo> owner = std::make_shared<foo>(42);
    lua["observer"] = std::weak_ptr<foo>(owner);

    // The observer works as expected
    lua.do_string("observer:bar()");

    // Reset the owner, so the observed object becomes invalid
    owner.reset();

    // We expect this to throw now; it does not! The call goes through.
    lua.do_string("observer:bar()");
}

Compiled with g++ -std=c++17 test.cpp -o test -I"." -I"/usr/include/lua5.2/" -llua5.2

I traced it down to lua_call_wrapper::call using sol::check_get() to fetch the this pointer. Function arguments use stack_detail::check_get_arg(), which is the only place in which the new customization point is used. Therefore, this does not go through this check.

I fixed this by adding the following just before the call to sol::check_get() (call.hpp:486):

if constexpr (meta::meta_detail::is_adl_sol_lua_check_access_v<Ta>) {
    stack::record tracking {};
    sol_lua_check_access(types<meta::unqualified_t<Ta>>(), L, 1, tracking);
}

I assume there are quite a few other places where this should be done, to cover all kinds of wrappers. Perhaps this is best added inside sol::check_get() to avoid duplication? I'm not sure why this was added only to stack_detail::check_get_arg() in the first place.

@cschreib
Copy link
Contributor Author

cschreib commented Mar 26, 2022

FYI this cschreib@f01daeb is how I fixed it on a fork, but I'm not sure this is 100% correct. It also required my custom sol_lua_check_access() to support T and T*:

template<typename T>
void sol_lua_check_access(sol::types<T>, lua_State* L, int index, sol::stack::record& tracking) {
    using ObjectType = std::remove_pointer_t<T>;

    sol::optional<std::weak_ptr<ObjectType>&> maybe_checked =
        sol::stack::check_get<std::weak_ptr<ObjectType>&>(L, index, sol::no_panic, tracking);

    if (!maybe_checked.has_value()) {
        return;
    }

    if (maybe_checked->expired()) {
        // We want to throw if the pointer has expired
        throw std::runtime_error("object has been deleted");
    }
}

I tried just adding the sol_lua_check_access customization to sol::check_get(), but that created a circular dependency (need to call sol::check_get() to get the pointer to check if it is valid). So I take it sol::check_get() is too low level for this.

@Spacechild1
Copy link

Just want to say that I've run into the very same issue!

According to #1074 (comment) sol_lua_check_access should also be called for member function calls (and also member variable access), so this really looks like a bug.

BTW, I could not find any official documentation for the sol_lua_check_access customization point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants