From 11d12aad117b43d7c2e73668510b5e02b58b58b9 Mon Sep 17 00:00:00 2001 From: BotoX Date: Mon, 16 Jan 2017 14:29:14 +0100 Subject: [PATCH] Extend function calling API for natives and allow catching exceptions. Change sourcepawn url. --- .gitmodules | 2 +- core/logic/AMBuilder | 1 + core/logic/NativeInvoker.cpp | 321 ++++++++++++++++++++++++++++++++++ core/logic/NativeInvoker.h | 79 +++++++++ core/logic/smn_functions.cpp | 62 +++++++ plugins/include/functions.inc | 25 +++ sourcepawn | 2 +- 7 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 core/logic/NativeInvoker.cpp create mode 100644 core/logic/NativeInvoker.h diff --git a/.gitmodules b/.gitmodules index fde0c20f..9ac38fcc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/alliedmodders/amtl [submodule "sourcepawn"] path = sourcepawn - url = https://github.com/alliedmodders/sourcepawn + url = https://github.com/BotoX/sourcepawn.git diff --git a/core/logic/AMBuilder b/core/logic/AMBuilder index 1415faa4..21418c8a 100644 --- a/core/logic/AMBuilder +++ b/core/logic/AMBuilder @@ -85,6 +85,7 @@ for arch in SM.archs: 'smn_halflife.cpp', 'FrameIterator.cpp', 'DatabaseConfBuilder.cpp', + 'NativeInvoker.cpp', ] if arch == 'x64': diff --git a/core/logic/NativeInvoker.cpp b/core/logic/NativeInvoker.cpp new file mode 100644 index 00000000..73658317 --- /dev/null +++ b/core/logic/NativeInvoker.cpp @@ -0,0 +1,321 @@ +// vim: set sts=2 ts=8 sw=2 tw=99 et: +// +// Copyright (C) 2006-2015 AlliedModders LLC +// +// This file is part of SourcePawn. SourcePawn is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// You should have received a copy of the GNU General Public License along with +// SourcePawn. If not, see http://www.gnu.org/licenses/. +// + +#include +#include +#include "NativeInvoker.h" + +/******************** +* FUNCTION CALLING * +********************/ + +NativeInvoker::NativeInvoker(IPluginContext *pContext, const ke::RefPtr &native) + : context_(pContext), + m_curparam(0), + m_errorstate(SP_ERROR_NONE), + native_(native) +{ +} + +NativeInvoker::~NativeInvoker() +{ + Cancel(); +} + +bool +NativeInvoker::IsRunnable() +{ + return true; +} + +IPluginContext * +NativeInvoker::GetParentContext() +{ + return context_; +} + +int NativeInvoker::PushCell(cell_t cell) +{ + if (m_curparam >= SP_MAX_EXEC_PARAMS) + return SetError(SP_ERROR_PARAMS_MAX); + + m_info[m_curparam].marked = false; + m_params[m_curparam] = cell; + m_curparam++; + + return SP_ERROR_NONE; +} + +int +NativeInvoker::PushCellByRef(cell_t *cell, int flags) +{ + return PushArray(cell, 1, flags); +} + +int +NativeInvoker::PushFloat(float number) +{ + cell_t val = sp::FloatCellUnion(number).cell; + + return PushCell(val); +} + +int +NativeInvoker::PushFloatByRef(float *number, int flags) +{ + return PushCellByRef((cell_t *)number, flags); +} + +int +NativeInvoker::PushArray(cell_t *inarray, unsigned int cells, int copyback) +{ + if (m_curparam >= SP_MAX_EXEC_PARAMS) + { + return SetError(SP_ERROR_PARAMS_MAX); + } + + ParamInfo *info = &m_info[m_curparam]; + + info->flags = inarray ? copyback : 0; + info->marked = true; + info->size = cells; + info->str.is_sz = false; + info->orig_addr = inarray; + + m_curparam++; + + return SP_ERROR_NONE; +} + +int +NativeInvoker::PushString(const char *string) +{ + return _PushString(string, SM_PARAM_STRING_COPY, 0, strlen(string)+1); +} + +int +NativeInvoker::PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags) +{ + return _PushString(buffer, sz_flags, cp_flags, length); +} + +int +NativeInvoker::_PushString(const char *string, int sz_flags, int cp_flags, size_t len) +{ + if (m_curparam >= SP_MAX_EXEC_PARAMS) + return SetError(SP_ERROR_PARAMS_MAX); + + ParamInfo *info = &m_info[m_curparam]; + + info->marked = true; + info->orig_addr = (cell_t *)string; + info->flags = cp_flags; + info->size = len; + info->str.sz_flags = sz_flags; + info->str.is_sz = true; + + m_curparam++; + + return SP_ERROR_NONE; +} + +void +NativeInvoker::Cancel() +{ + if (!m_curparam) + return; + + m_errorstate = SP_ERROR_NONE; + m_curparam = 0; +} + +int +NativeInvoker::Execute(cell_t *result, cell_t buffer, cell_t size) +{ + context_->ClearLastNativeError(); + + // For backward compatibility, we have to clear the exception state. + // Otherwise code like this: + // + // static cell_t native(cx, params) { + // for (auto callback : callbacks) { + // callback->Execute(); + // } + // } + // + // Could unintentionally leak a pending exception back to the caller, + // which wouldn't have happened before the Great Exception Refactoring. + + SourcePawn::ExceptionHandler eh(context_); + eh.Debug(!size); + + if (!Invoke(result)) { + if(size) + context_->StringToLocalUTF8(buffer, size, eh.Message(), NULL); + int Err = context_->GetLastNativeError(); + context_->ClearLastNativeError(); + return Err; + } + + return SP_ERROR_NONE; +} + +bool +NativeInvoker::Invoke(cell_t *result) +{ + if (!IsRunnable()) { + Cancel(); + context_->ReportErrorNumber(SP_ERROR_NOT_RUNNABLE); + return false; + } + if (int err = m_errorstate) { + Cancel(); + context_->ReportErrorNumber(err); + return false; + } + + //This is for re-entrancy! + cell_t _temp_params[SP_MAX_EXEC_PARAMS + 1]; + cell_t *temp_params = &_temp_params[1]; + ParamInfo temp_info[SP_MAX_EXEC_PARAMS]; + unsigned int numparams = m_curparam; + unsigned int i; + + if (numparams) + { + //Save the info locally, then reset it for re-entrant calls. + memcpy(temp_info, m_info, numparams * sizeof(ParamInfo)); + } + m_curparam = 0; + + /* Initialize 0th parameter */ + _temp_params[0] = numparams; + + /* Browse the parameters and build arrays */ + bool ok = true; + for (i=0; iHeapAlloc( + temp_info[i].size, + &(temp_info[i].local_addr), + &(temp_info[i].phys_addr)); + if (err != SP_ERROR_NONE) { + context_->ReportErrorNumber(err); + ok = false; + break; + } + if (temp_info[i].orig_addr) + { + memcpy(temp_info[i].phys_addr, temp_info[i].orig_addr, sizeof(cell_t) * temp_info[i].size); + } + } else { + /* Calculate cells required for the string */ + size_t cells = (temp_info[i].size + sizeof(cell_t) - 1) / sizeof(cell_t); + + /* Allocate the buffer */ + int err = context_->HeapAlloc( + cells, + &(temp_info[i].local_addr), + &(temp_info[i].phys_addr)); + if (err != SP_ERROR_NONE) { + context_->ReportErrorNumber(err); + ok = false; + break; + } + + /* Copy original string if necessary */ + if ((temp_info[i].str.sz_flags & SM_PARAM_STRING_COPY) && (temp_info[i].orig_addr != NULL)) + { + /* Cut off UTF-8 properly */ + if (temp_info[i].str.sz_flags & SM_PARAM_STRING_UTF8) { + context_->StringToLocalUTF8( + temp_info[i].local_addr, + temp_info[i].size, + (const char *)temp_info[i].orig_addr, + NULL); + } + /* Copy a binary blob */ + else if (temp_info[i].str.sz_flags & SM_PARAM_STRING_BINARY) + { + memmove(temp_info[i].phys_addr, temp_info[i].orig_addr, temp_info[i].size); + } + /* Copy ASCII characters */ + else + { + context_->StringToLocal( + temp_info[i].local_addr, + temp_info[i].size, + (const char *)temp_info[i].orig_addr); + } + } + } /* End array/string calculation */ + /* Update the pushed parameter with the byref local address */ + temp_params[i] = temp_info[i].local_addr; + } else { + /* Just copy the value normally */ + temp_params[i] = m_params[i]; + } + } + + /* Make the call if we can */ + if (ok) + { + *result = native_->func()(context_, _temp_params); + } + + /* i should be equal to the last valid parameter + 1 */ + bool docopies = ok; + while (i--) { + if (!temp_info[i].marked) + continue; + + if (docopies && (temp_info[i].flags & SM_PARAM_COPYBACK)) { + if (temp_info[i].orig_addr) { + if (temp_info[i].str.is_sz) { + memcpy(temp_info[i].orig_addr, temp_info[i].phys_addr, temp_info[i].size); + + } else { + if (temp_info[i].size == 1) { + *temp_info[i].orig_addr = *(temp_info[i].phys_addr); + } else { + memcpy(temp_info[i].orig_addr, + temp_info[i].phys_addr, + temp_info[i].size * sizeof(cell_t)); + } + } + } + } + + if (int err = context_->HeapPop(temp_info[i].local_addr)) + context_->ReportErrorNumber(err); + } + + return context_->GetLastNativeError() == SP_ERROR_NONE; +} + +int +NativeInvoker::SetError(int err) +{ + m_errorstate = err; + + return err; +} + +int NativeInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) { return 0; } +funcid_t NativeInvoker::GetFunctionID() { return 0; } +int NativeInvoker::Execute2(IPluginContext *ctx, cell_t *result) { return 0; } +int NativeInvoker::CallFunction2(IPluginContext *ctx, const cell_t *params, unsigned int num_params, cell_t *result) { return 0; } +IPluginRuntime *NativeInvoker::GetParentRuntime() { return NULL; } diff --git a/core/logic/NativeInvoker.h b/core/logic/NativeInvoker.h new file mode 100644 index 00000000..c16d5ea1 --- /dev/null +++ b/core/logic/NativeInvoker.h @@ -0,0 +1,79 @@ +// vim: set sts=2 ts=8 sw=2 tw=99 et: +// +// Copyright (C) 2006-2015 AlliedModders LLC +// +// This file is part of SourcePawn. SourcePawn is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// You should have received a copy of the GNU General Public License along with +// SourcePawn. If not, see http://www.gnu.org/licenses/. +// +#ifndef _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ +#define _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ + +#include +#include +#include +#include "Native.h" + +struct ParamInfo +{ + int flags; /* Copy-back flags */ + bool marked; /* Whether this is marked as being used */ + cell_t local_addr; /* Local address to free */ + cell_t *phys_addr; /* Physical address of our copy */ + cell_t *orig_addr; /* Original address to copy back to */ + ucell_t size; /* Size of array in bytes */ + struct { + bool is_sz; /* is a string */ + int sz_flags; /* has sz flags */ + } str; +}; + +class NativeInvoker : public IPluginFunction +{ + public: + NativeInvoker(IPluginContext *pContext, const ke::RefPtr &native); + virtual ~NativeInvoker(); + + public: + int PushCell(cell_t cell); + int PushCellByRef(cell_t *cell, int flags); + int PushFloat(float number); + int PushFloatByRef(float *number, int flags); + int PushArray(cell_t *inarray, unsigned int cells, int copyback); + int PushString(const char *string); + int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags); + int Execute(cell_t *result, cell_t buffer=0, cell_t size=0); + void Cancel(); + int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result); + IPluginContext *GetParentContext(); + bool Invoke(cell_t *result); + bool IsRunnable(); + funcid_t GetFunctionID(); + int Execute2(IPluginContext *ctx, cell_t *result); + int CallFunction2(IPluginContext *ctx, + const cell_t *params, + unsigned int num_params, + cell_t *result); + IPluginRuntime *GetParentRuntime(); + const char *DebugName() { + return native_->name(); + } + + private: + int _PushString(const char *string, int sz_flags, int cp_flags, size_t len); + int SetError(int err); + + private: + IPluginContext *context_; + cell_t m_params[SP_MAX_EXEC_PARAMS]; + ParamInfo m_info[SP_MAX_EXEC_PARAMS]; + unsigned int m_curparam; + int m_errorstate; + ke::RefPtr native_; +}; + +#endif //_INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ diff --git a/core/logic/smn_functions.cpp b/core/logic/smn_functions.cpp index 606b7855..d536f57f 100644 --- a/core/logic/smn_functions.cpp +++ b/core/logic/smn_functions.cpp @@ -35,6 +35,8 @@ #include #include #include +#include "ShareSys.h" +#include "NativeInvoker.h" HandleType_t g_GlobalFwdType = 0; HandleType_t g_PrivateFwdType = 0; @@ -43,6 +45,7 @@ static bool s_CallStarted = false; static ICallable *s_pCallable = NULL; static IPluginFunction *s_pFunction = NULL; static IForward *s_pForward = NULL; +static NativeInvoker *s_pInvoker = NULL; class ForwardNativeHelpers : public SMGlobalClass, @@ -102,6 +105,9 @@ inline void ResetCall() s_pFunction = NULL; s_pForward = NULL; s_pCallable = NULL; + if(s_pInvoker) + delete s_pInvoker; + s_pInvoker = NULL; } static cell_t sm_GetFunctionByName(IPluginContext *pContext, const cell_t *params) @@ -366,6 +372,27 @@ static cell_t sm_CallStartForward(IPluginContext *pContext, const cell_t *params return 1; } +static cell_t sm_CallStartNative(IPluginContext *pContext, const cell_t *params) +{ + ResetCall(); + + char *name; + pContext->LocalToString(params[1], &name); + + ke::RefPtr pNative = g_ShareSys.FindNative(name); + + if (!pNative) + return 0;//pContext->ThrowNativeError("Invalid native \"%s\"", name); + + s_pInvoker = new NativeInvoker(pContext, pNative); + + s_pCallable = static_cast(s_pInvoker); + + s_CallStarted = true; + + return 1; +} + static cell_t sm_CallPushCell(IPluginContext *pContext, const cell_t *params) { int err; @@ -656,6 +683,39 @@ static cell_t sm_CallFinish(IPluginContext *pContext, const cell_t *params) IForward *pForward = s_pForward; ResetCall(); err = pForward->Execute(result, NULL); + } else if (s_pInvoker) { + err = s_pInvoker->Execute(result); + ResetCall(); + } + + return err; +} + +static cell_t sm_CallFinishEx(IPluginContext *pContext, const cell_t *params) +{ + int err = SP_ERROR_NOT_RUNNABLE; + cell_t *result; + + if (!s_CallStarted) + { + return pContext->ThrowNativeError("Cannot finish call when there is no call in progress"); + } + + pContext->LocalToPhysAddr(params[1], &result); + + // Note: Execute() swallows exceptions, so this is okay. + if (s_pFunction) + { + IPluginFunction *pFunction = s_pFunction; + ResetCall(); + err = pFunction->Execute(result, params[2], params[3]); + } else if (s_pForward) { + IForward *pForward = s_pForward; + ResetCall(); + err = pForward->Execute(result, NULL); + } else if (s_pInvoker) { + err = s_pInvoker->Execute(result, params[2], params[3]); + ResetCall(); } return err; @@ -742,6 +802,7 @@ REGISTER_NATIVES(functionNatives) {"RemoveAllFromForward", sm_RemoveAllFromForward}, {"Call_StartFunction", sm_CallStartFunction}, {"Call_StartForward", sm_CallStartForward}, + {"Call_StartNative", sm_CallStartNative}, {"Call_PushCell", sm_CallPushCell}, {"Call_PushCellRef", sm_CallPushCellRef}, {"Call_PushFloat", sm_CallPushFloat}, @@ -753,6 +814,7 @@ REGISTER_NATIVES(functionNatives) {"Call_PushNullVector", sm_CallPushNullVector}, {"Call_PushNullString", sm_CallPushNullString}, {"Call_Finish", sm_CallFinish}, + {"Call_FinishEx", sm_CallFinishEx}, {"Call_Cancel", sm_CallCancel}, {"RequestFrame", sm_AddFrameAction}, diff --git a/plugins/include/functions.inc b/plugins/include/functions.inc index 925040ce..2c1fab3b 100644 --- a/plugins/include/functions.inc +++ b/plugins/include/functions.inc @@ -293,6 +293,17 @@ native void Call_StartForward(Handle fwd); */ native void Call_StartFunction(Handle plugin, Function func); +/** + * Starts a call to a native. + * + * @note Cannot be used during an incomplete call. + * + * @param name Name of the native. + * @return True on success, false otherwise. + * @error Invalid function, or called before another call has completed. + */ +native bool Call_StartNative(const char[] name); + /** * Pushes a cell onto the current call. * @@ -416,6 +427,20 @@ native void Call_PushNullString(); */ native int Call_Finish(any &result=0); +/** + * Completes a call to a function or forward's call list. + * Catches exceptions thrown by the native. + * + * @note Cannot be used before a call has been started. + * + * @param result Return value of function or forward's call list. + * @param exception Buffer to store the exception in. + * @param maxlength Maximum length of the buffer. + * @return SP_ERROR_NONE on success, any other integer on failure. + * @error Called before a call has been started. + */ +native int Call_FinishEx(any &result=0, char[] exception, int maxlength); + /** * Cancels a call to a function or forward's call list. * diff --git a/sourcepawn b/sourcepawn index 7ba3e384..04eafd88 160000 --- a/sourcepawn +++ b/sourcepawn @@ -1 +1 @@ -Subproject commit 7ba3e384e29ccdb5dbd3ac4a0fda16fd0a0144a8 +Subproject commit 04eafd88631e7a3ba1de6bc7228af0e3d5443f0b