Skip to content

Commit

Permalink
feat: add new allowYield parameter to newcclosure; decrease detection…
Browse files Browse the repository at this point in the history
…s; improve hooks; stuff
  • Loading branch information
SecondNewtonLaw committed Oct 4, 2024
1 parent 42005ad commit 5d1a1ce
Show file tree
Hide file tree
Showing 12 changed files with 443 additions and 234 deletions.
120 changes: 101 additions & 19 deletions ClosureManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//
#include "ClosureManager.hpp"
#include <lua.h>
#include <regex>

#include "Communication/Communication.hpp"
#include "Environment/Libraries/Globals.hpp"
Expand Down Expand Up @@ -34,55 +35,122 @@ void ClosureManager::ResetManager(const RBX::DataModelType &resetTarget) {

int ClosureManager::newcclosure_handler(lua_State *L) {
const auto argc = lua_gettop(L);
const auto manager = RobloxManager::GetSingleton();
const auto robloxManager = RobloxManager::GetSingleton();

const auto scriptContext = manager->GetScriptContext(L);
const auto scriptContext = robloxManager->GetScriptContext(L);
if (!scriptContext.has_value())
luaL_error(L, "call resolution failed; ScriptContext unavailable");

const auto dataModel = manager->GetDataModelFromScriptContext(scriptContext.value());
const auto dataModel = robloxManager->GetDataModelFromScriptContext(scriptContext.value());
if (!dataModel.has_value())
luaL_error(L, "call resolution failed; DataModel unavailable");

const auto currentDataModel = manager->GetDataModelType(dataModel.value());
const auto currentDataModel = robloxManager->GetDataModelType(dataModel.value());

const auto clManager = ClosureManager::GetSingleton();
if (!clManager->m_newcclosureMap[currentDataModel].contains(clvalue(L->ci->func)))
luaL_error(L, "call resolution failed"); // using key based indexing will insert it into the map, that is wrong.

// Due to the nature of the Lua Registry, our closures can be replaced with complete garbage.
// Thus we must be careful, and validate that the Lua Registry ref IS present.
Closure *closure = clManager->m_newcclosureMap[currentDataModel].at(clvalue(L->ci->func));
ClosureWrapInformation closure = clManager->m_newcclosureMap[currentDataModel].at(clvalue(L->ci->func));

luaC_threadbarrier(L);

L->top->value.p = closure;
L->top->value.p = closure.closure;
L->top->tt = lua_Type::LUA_TFUNCTION;
L->top++;

const auto scheduler = Scheduler::GetSingleton();

if (!RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel].contains(closure))
if (!RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel].contains(closure.closure))
luaL_error(L, "call resolution failed, invalid closure state.");
lua_getref(L, RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel][closure]);
if (lua_type(L, -1) == LUA_TFUNCTION && lua_toclosure(L, -1) == closure) {
lua_getref(L, RbxStu::s_mRefsMapBasedOnDataModel[currentDataModel][closure.closure]);
if (lua_type(L, -1) == LUA_TFUNCTION && lua_toclosure(L, -1) == closure.closure) {
lua_pop(L, 1);
} else {
luaL_error(L, "call resolution failed, invalid ref."); // If the pointers aren't the same, and the lua type
// isn't correct either, this MUST mean this is the work
// on the devil, but we don't have a crucifix to remove
// him, so just crash.
}

lua_insert(L, 1);

if (closure.requiresYielding) {
/*
* Due to the very clear possibility of the current thread not being yieldable, we must re-schedule the closure
* into a new thread, then we must exchange the returns of that thread into our own, whilst keeping this one
* waiting for whatever that function returns (Yielding on this side until that other function answers and
* fulfills the request!)
*/

if (robloxManager->GetRobloxTaskDefer().has_value()) {
const auto defer = robloxManager->GetRobloxTaskDefer().value();
defer(L);
} else if (robloxManager->GetRobloxTaskSpawn().has_value()) {
const auto spawn = robloxManager->GetRobloxTaskSpawn().value();
spawn(L);
} else {
throw std::exception("cannot handle yieldable newcclosure; no schedule function available");
}

const auto nL = lua_tothread(L, -1);
nL->namecall = L->namecall;

scheduler->ScheduleJob(SchedulerJob(
L, [nL](lua_State *L, std::shared_future<std::function<int(lua_State *)>> *callbackToExecute) {
printf("waiting for ex...\n");
while (lua_costatus(L, nL) != lua_CoStatus::LUA_COFIN &&
lua_costatus(L, nL) != lua_CoStatus::LUA_COERR) {
_mm_pause();
}
printf("call complete!\n");

// ready to resume into original thread, the call has been successfully dispatched.

*callbackToExecute = std::async(std::launch::async, [nL]() -> std::function<int(lua_State *)> {
return [nL](lua_State *L) -> int {
if (lua_status(nL) == lua_CoStatus::LUA_COERR) {
// We must move to the original thread again, except just the first argument and signal
// the scheduler the task failed.
lua_xmove(nL, L, 1);

/*
* When we throw from Luau, we are using Roblox's rawrununprotected and we are going
* through its VM this leads for our pusherror modificaton to never run; meaning the
* callstack information will be present on the error message. To prevent this, we must
* manually strip the data with a few string operations. this guarantees our metamethod
* hooks to be undetected, whilst the error message doesn't change.
*/
const auto newError = Utilities::StripLuaErrorMessage(lua_tostring(L, -1));
lua_pop(L, 1);
lua_pushstring(L, newError.c_str());
return -1;
}

lua_xmove(nL, L, lua_gettop(nL));

return lua_gettop(L);
};
});
}));

return lua_yield(L, 0);
}

const auto callResult = lua_pcall(L, argc, LUA_MULTRET, 0);
if (callResult != LUA_OK && callResult != LUA_YIELD &&
std::strcmp(luaL_optstring(L, -1, ""), "attempt to yield across metamethod/C-call boundary") == 0) {
return lua_yield(L, LUA_MULTRET);
}

if (callResult == LUA_ERRRUN)
if (callResult == LUA_ERRRUN) {
const auto newError = Utilities::StripLuaErrorMessage(lua_tostring(L, -1));
lua_pop(L, 1);
lua_pushstring(L, newError.c_str());
lua_error(L); // error string at stack top
}

return lua_gettop(L);
}
Expand All @@ -107,9 +175,9 @@ bool ClosureManager::IsWrapped(lua_State *L, const Closure *closure) {

const auto currentDataModel = manager->GetDataModelType(dataModel.value());

return std::ranges::any_of(this->m_newcclosureMap[currentDataModel].begin(),
this->m_newcclosureMap[currentDataModel].end(),
[&closure](const std::pair<Closure *, Closure *> cl) { return closure == cl.second; });
return std::ranges::any_of(
this->m_newcclosureMap[currentDataModel].begin(), this->m_newcclosureMap[currentDataModel].end(),
[&closure](const std::pair<Closure *, ClosureWrapInformation> cl) { return closure == cl.second.closure; });
}

int ClosureManager::hookfunction(lua_State *L) {
Expand Down Expand Up @@ -200,7 +268,7 @@ int ClosureManager::hookfunction(lua_State *L) {
[hookTarget]; // We must substitute the refs on original to point to the new,
// but we must keep the clones'.

clManager->m_newcclosureMap[currentDataModel][hookTarget] = hookWith;
clManager->m_newcclosureMap[currentDataModel][hookTarget] = {hookWith, false};
L->top->value.p = cl;
L->top->tt = LUA_TFUNCTION;
L->top++;
Expand Down Expand Up @@ -341,13 +409,27 @@ void ClosureManager::FixClosure(lua_State *L, Closure *closure) {
int ClosureManager::newcclosure(lua_State *L) {
// Using relative offsets, since this function may be called by other functions! (hookfunc)
auto closureIndex = -1;
if (lua_type(L, -1) == LUA_TSTRING)
auto nameIndex = -1;
if (lua_type(L, -2) == LUA_TSTRING || lua_type(L, -2) == LUA_TNIL) {
closureIndex--;
closureIndex--;
nameIndex--;
}

if (lua_type(L, -1) == LUA_TSTRING || lua_type(L, -1) == LUA_TNIL) {
closureIndex--;
}

luaL_checktype(L, closureIndex, LUA_TFUNCTION);
bool mustYield = false;

const char *functionName = nullptr;
if (lua_type(L, -1) == LUA_TSTRING)
functionName = luaL_optstring(L, -1, nullptr);

if (lua_type(L, nameIndex) == LUA_TSTRING)
functionName = luaL_optstring(L, nameIndex, nullptr);

if (lua_type(L, nameIndex + 1) == LUA_TBOOLEAN)
mustYield = static_cast<bool>(lua_toboolean(L, nameIndex + 1));

const auto manager = RobloxManager::GetSingleton();

Expand All @@ -361,8 +443,8 @@ int ClosureManager::newcclosure(lua_State *L) {
const auto closure = lua_toclosure(L, closureIndex);
clManager->FixClosure(L, closure);
lua_pushcclosurek(L, ClosureManager::newcclosure_handler, functionName, 0, nullptr);
const auto cclosure = lua_toclosure(L, closureIndex);
clManager->m_newcclosureMap[currentDataModel][cclosure] = closure;
const auto cclosure = lua_toclosure(L, -1);
clManager->m_newcclosureMap[currentDataModel][cclosure] = ClosureWrapInformation{closure, mustYield};
lua_remove(L, lua_gettop(L) - 1); // Balance lua stack.
return 1;
}
Expand Down
7 changes: 6 additions & 1 deletion ClosureManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ struct LuauHookInformation {
Closure *hookedClosure;
};

struct ClosureWrapInformation {
Closure *closure;
bool requiresYielding;
};

class ClosureManager final {
static std::shared_ptr<ClosureManager> pInstance;

std::map<RBX::DataModelType, std::map<Closure *, LuauHookInformation>>
m_hookMap; // TODO: Make hook map use DataModel Types'
std::map<RBX::DataModelType, std::map<Closure *, Closure *>> m_newcclosureMap;
std::map<RBX::DataModelType, std::map<Closure *, ClosureWrapInformation>> m_newcclosureMap;

/// @brief Handles a newcclosure call.
static int newcclosure_handler(lua_State *L);
Expand Down
2 changes: 1 addition & 1 deletion Dependencies/Luau/VM/src/ldebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ l_noret luaG_readonlyerror(lua_State* L)
static void pusherror(lua_State* L, const char* msg)
{
CallInfo* ci = L->ci;
if (isLua(ci))
if (isLua(ci) && clvalue(ci->func)->l.p->linedefined != -1)
{
TString* source = getluaproto(ci)->source;
char chunkbuf[LUA_IDSIZE]; // add file:line information
Expand Down
3 changes: 2 additions & 1 deletion Dependencies/Luau/VM/src/ldo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud)
->GetOffset("luaD_rawrununprotected"))(L, f, ud);

int status = 0;

printf("Using RbxStu V2's luaD_rawrununprotected!\n");
try
{
f(L, ud);
Expand Down Expand Up @@ -160,6 +160,7 @@ l_noret luaD_throw(lua_State* L, int errcode)
{
if (nullptr != RbxStuOffsets::GetSingleton()->GetOffset("luaD_throw"))
return reinterpret_cast<RBX::Studio::FunctionTypes::luaD_throw>(RbxStuOffsets::GetSingleton()->GetOffset("luaD_throw"))(L, errcode);
printf("Using RbxStu V2's luaD_throw!\n");
// throw lua_exception(L, errcode);
}
#endif
Expand Down
19 changes: 18 additions & 1 deletion Dependencies/Luau/VM/src/lgc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ static const char* gettablemode(global_State* g, Table* h)

static int traversetable(global_State* g, Table* h)
{
return 0;

int i;
int weakkey = 0;
int weakvalue = 0;
Expand Down Expand Up @@ -700,6 +702,8 @@ static void markroot(lua_State* L)

static size_t remarkupvals(global_State* g)
{
return 0;

size_t work = 0;

for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
Expand All @@ -719,6 +723,8 @@ static size_t remarkupvals(global_State* g)

static size_t clearupvals(lua_State* L)
{
return 0;

global_State* g = L->global;

size_t work = 0;
Expand Down Expand Up @@ -753,6 +759,7 @@ static size_t clearupvals(lua_State* L)

static size_t atomic(lua_State* L)
{
return 0;
global_State* g = L->global;
LUAU_ASSERT(g->gcstate == GCSatomic);

Expand Down Expand Up @@ -818,6 +825,8 @@ static size_t atomic(lua_State* L)
// a version of generic luaM_visitpage specialized for the main sweep stage
static int sweepgcopage(lua_State* L, lua_Page* page)
{
return 0;

char* start;
char* end;
int busyBlocks;
Expand Down Expand Up @@ -864,6 +873,8 @@ static int sweepgcopage(lua_State* L, lua_Page* page)

static size_t gcstep(lua_State* L, size_t limit)
{
return 0;

size_t cost = 0;
global_State* g = L->global;
switch (g->gcstate)
Expand Down Expand Up @@ -961,6 +972,8 @@ static size_t gcstep(lua_State* L, size_t limit)

static int64_t getheaptriggererroroffset(global_State* g)
{
return 0;

// adjust for error using Proportional-Integral controller
// https://en.wikipedia.org/wiki/PID_controller
int32_t errorKb = int32_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.heapgoalsizebytes) / 1024);
Expand Down Expand Up @@ -993,6 +1006,8 @@ static int64_t getheaptriggererroroffset(global_State* g)

static size_t getheaptrigger(global_State* g, size_t heapgoal)
{
return 0;

// adjust threshold based on a guess of how many bytes will be allocated between the cycle start and sweep phase
// our goal is to begin the sweep when used memory has reached the heap goal
const double durationthreshold = 1e-3;
Expand All @@ -1016,7 +1031,8 @@ static size_t getheaptrigger(global_State* g, size_t heapgoal)
size_t luaC_step(lua_State* L, bool assist)
{
return 0;
//return reinterpret_cast<RBX::Studio::FunctionTypes::luaC_Step>(RbxStuOffsets::GetSingleton()->GetOffset("luaC_Step"))(L, assist);
printf("running luaC_step\n");
// return reinterpret_cast<RBX::Studio::FunctionTypes::luaC_Step>(RbxStuOffsets::GetSingleton()->GetOffset("luaC_Step"))(L, assist);

global_State* g = L->global;

Expand Down Expand Up @@ -1080,6 +1096,7 @@ size_t luaC_step(lua_State* L, bool assist)

void luaC_fullgc(lua_State* L)
{
return;
global_State* g = L->global;

#ifdef LUAI_GCMETRICS
Expand Down
1 change: 1 addition & 0 deletions Dependencies/Luau/VM/src/lvmexecute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3043,6 +3043,7 @@ void luau_execute(lua_State* L)
{
return reinterpret_cast<RBX::Studio::FunctionTypes::luau_execute>(RbxStuOffsets::GetSingleton()->GetOffset("luau_execute"))(L);

printf("Utilizing RbxStu V2 luau_execute!\n");
if (L->singlestep)
luau_execute<true>(L);
else
Expand Down
Loading

0 comments on commit 5d1a1ce

Please sign in to comment.