mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
174 lines
6.1 KiB
C++
174 lines
6.1 KiB
C++
#include "rpc.h"
|
|
|
|
#include "executor/GameStateExecutionContext.h"
|
|
#include "executor/global_executors.h"
|
|
#include "parse/encrypt.h"
|
|
#include "network/multi_lua.h"
|
|
#include "scripting/ade_api.h"
|
|
#include "scripting/api/objs/execution_context.h"
|
|
#include "scripting/api/objs/executor.h"
|
|
#include "scripting/api/objs/promise.h"
|
|
|
|
namespace scripting {
|
|
namespace api {
|
|
|
|
rpc_h rpc_h_impl::create(lua_State* L, SCP_string name) {
|
|
rpc_h newRPC{ new rpc_h_impl(std::move(name)) };
|
|
if (!add_rpc(newRPC)) {
|
|
LuaError(L, "This name is already in use for an RPC on this machine.\nIf you are using different RPC's for different "
|
|
"machines with the same name, make sure that each machine actually only initializes one RPC object.");
|
|
return nullptr;
|
|
}
|
|
return newRPC;
|
|
}
|
|
|
|
rpc_h_impl::rpc_h_impl(SCP_string _name) : hash(static_cast<ushort>(hash_fnv1a(_name) & lua_net_bitmask_rpchash /* : 13 */)), name(std::move(_name)) { }
|
|
|
|
rpc_h_impl::~rpc_h_impl() {
|
|
//This destructor needs to unregister the function. Once this gets called, we have no further access to the RPC method. It can't be called anymore, and shouldn't recieve packets
|
|
|
|
//cleaing the RPC-Repositories weak_ptr list here causes UB per LWG issue 2751. However, since this is just cleanup,
|
|
//it doesn't matter to us whether the weak_ptr pointing to this very object is cleared as well.
|
|
//Worst case, and it'll be cleared when the next RPC object gets created or destroyed.
|
|
clean_rpc_refs();
|
|
}
|
|
|
|
//**********HANDLE: Remote Procedure Call
|
|
ADE_OBJ(l_RPC, rpc_h, "rpc", "A function object for remote procedure calls");
|
|
|
|
ADE_FUNC(__newindex, l_RPC, "function(any arg) => void rpc_body", "Sets the function to be called when the RPC is invoked on this client", "function(any arg) => void", "The function the RPC is set to")
|
|
{
|
|
rpc_h rpc;
|
|
luacpp::LuaFunction func;
|
|
if (!ade_get_args(L, "ou", l_RPC.Get(&rpc), &func))
|
|
return ade_set_error(L, "u", rpc->func);
|
|
|
|
if (rpc == nullptr)
|
|
return ade_set_error(L, "b", false);
|
|
|
|
rpc->func = std::move(func);
|
|
|
|
return ade_set_args(L, "u", rpc->func);
|
|
}
|
|
|
|
ADE_FUNC(__call, l_RPC, "[any = nil, enumeration recipient /* RPC_* */]", "Calls the RPC on the specified RPC_* recipients with the given argument. The recipient defaults to what was set on RPC creation.", "boolean", "True, if RPC call happened (not a guarantee for arrival at the recipient!)")
|
|
{
|
|
rpc_h rpc;
|
|
luacpp::LuaValue argument;
|
|
enum_h recipient;
|
|
if (!ade_get_args(L, "o|ao", l_RPC.Get(&rpc), &argument, l_Enum.Get(&recipient)))
|
|
return ade_set_error(L, "b", false);
|
|
|
|
if(rpc == nullptr)
|
|
return ade_set_error(L, "b", false);
|
|
|
|
if(!argument.isValid())
|
|
argument = luacpp::LuaValue::createNil(L);
|
|
|
|
if (!recipient.isValid() || (recipient.index != LE_RPC_SERVER && recipient.index != LE_RPC_CLIENTS && recipient.index != LE_RPC_BOTH))
|
|
recipient = rpc->recipient;
|
|
|
|
lua_net_reciever recipient_lua;
|
|
|
|
switch (recipient.index) {
|
|
case LE_RPC_SERVER:
|
|
recipient_lua = lua_net_reciever::SERVER;
|
|
break;
|
|
case LE_RPC_CLIENTS:
|
|
recipient_lua = lua_net_reciever::CLIENTS;
|
|
break;
|
|
case LE_RPC_BOTH:
|
|
recipient_lua = lua_net_reciever::BOTH;
|
|
break;
|
|
default:
|
|
Assertion(false, "RPC recipient enum is bad! Get a programmer!");
|
|
return ade_set_error(L, "b", false);
|
|
}
|
|
|
|
lua_net_mode mode_lua;
|
|
|
|
switch (rpc->mode.index) {
|
|
case LE_RPC_RELIABLE:
|
|
mode_lua = lua_net_mode::RELIABLE;
|
|
break;
|
|
case LE_RPC_ORDERED:
|
|
mode_lua = lua_net_mode::ORDERED;
|
|
break;
|
|
case LE_RPC_UNRELIABLE:
|
|
mode_lua = lua_net_mode::UNRELIABLE;
|
|
break;
|
|
default:
|
|
Assertion(false, "RPC mode enum is bad! Get a programmer!");
|
|
return ade_set_error(L, "b", false);
|
|
}
|
|
|
|
bool sent = send_lua_packet(argument, rpc->hash, mode_lua, recipient_lua);
|
|
|
|
return ade_set_args(L, "b", sent);
|
|
}
|
|
|
|
ADE_FUNC(waitRPC,
|
|
l_RPC,
|
|
nullptr,
|
|
"Performs an asynchronous wait until this RPC has been evoked on this client and the RPC function has finished running. Does NOT trigger when the RPC is called from this client.",
|
|
"promise",
|
|
"A promise with no return value that resolves when this RPC has been called the next time.")
|
|
{
|
|
rpc_h rpc;
|
|
if (!ade_get_args(L, "o", l_RPC.Get(&rpc)))
|
|
return ADE_RETURN_NIL;
|
|
|
|
if (rpc == nullptr)
|
|
return ADE_RETURN_NIL;
|
|
|
|
class rpc_resolve_context : public resolve_context, public std::enable_shared_from_this<rpc_resolve_context> {
|
|
public:
|
|
rpc_resolve_context(rpc_h_ref rpc) : m_rpc(rpc), m_timestamp(rpc.lock()->lastCalled) {
|
|
static int unique_id_counter = 0;
|
|
m_unique_id = unique_id_counter++;
|
|
nprintf(("scripting", "waitRPC: Creating asynchronous context %d.\n", m_unique_id));
|
|
}
|
|
void setResolver(Resolver resolver) override
|
|
{
|
|
// Keep checking the time until the timestamp is elapsed
|
|
auto self = shared_from_this();
|
|
auto cb = [this, self, resolver = std::move(resolver)](
|
|
executor::IExecutionContext::State contextState) {
|
|
if (contextState == executor::IExecutionContext::State::Invalid) {
|
|
mprintf(("waitRPC: Context is invalid, possibly due to a game state change (current state is %s). Aborting asynchronous context %d.\n", GS_state_text[gameseq_get_state()], m_unique_id));
|
|
resolver(true, luacpp::LuaValueList());
|
|
return executor::Executor::CallbackResult::Done;
|
|
}
|
|
|
|
if (m_rpc.expired()) {
|
|
mprintf(("waitRPC: RPC got invalidated. Aborting asynchronous context %d.\n", m_unique_id));
|
|
resolver(true, luacpp::LuaValueList());
|
|
return executor::Executor::CallbackResult::Done;
|
|
}
|
|
|
|
if (ui_timestamp_compare(m_timestamp, m_rpc.lock()->lastCalled) > 0) {
|
|
nprintf(("scripting", "waitRPC: Timestamp has elapsed for asynchronous context %d.\n", m_unique_id));
|
|
resolver(false, luacpp::LuaValueList());
|
|
return executor::Executor::CallbackResult::Done;
|
|
}
|
|
|
|
return executor::Executor::CallbackResult::Reschedule;
|
|
};
|
|
|
|
// Use an game state execution context here to clean up references to this as soon as possible
|
|
executor::OnSimulationExecutor->post(
|
|
executor::runInContext(executor::GameStateExecutionContext::captureContext(), std::move(cb)));
|
|
}
|
|
|
|
private:
|
|
rpc_h_ref m_rpc;
|
|
UI_TIMESTAMP m_timestamp;
|
|
int m_unique_id = -1;
|
|
};
|
|
return ade_set_args(L,
|
|
"o",
|
|
l_Promise.Set(LuaPromise(std::make_shared<rpc_resolve_context>(rpc))));
|
|
}
|
|
|
|
}
|
|
} |