diff --git a/core/ConCmdManager.cpp b/core/ConCmdManager.cpp index e93c6cc3..26660767 100644 --- a/core/ConCmdManager.cpp +++ b/core/ConCmdManager.cpp @@ -218,6 +218,7 @@ ResultType ConCmdManager::DispatchClientCommand(int client, const char *cmd, int hook->pf->PushCell(args); cell_t tempres = result; + if (hook->pf->Execute(&tempres) == SP_ERROR_NONE) { if (tempres > result) diff --git a/core/logic/AMBuilder b/core/logic/AMBuilder index ba3d064a..a22d8751 100644 --- a/core/logic/AMBuilder +++ b/core/logic/AMBuilder @@ -62,7 +62,6 @@ binary.sources += [ 'PluginSys.cpp', 'HandleSys.cpp', 'NativeOwner.cpp', - 'NativeInvoker.cpp', 'ExtensionSys.cpp', 'DebugReporter.cpp', 'Database.cpp', diff --git a/core/logic/DebugReporter.cpp b/core/logic/DebugReporter.cpp index ef5863ba..4b1199f1 100644 --- a/core/logic/DebugReporter.cpp +++ b/core/logic/DebugReporter.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -132,51 +132,6 @@ void DebugReport::GenerateCodeError(IPluginContext *pContext, uint32_t code_addr } } -void DebugReport::OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) -{ - const char *lastname; - const char *plname = pluginsys->FindPluginByContext(ctx->GetContext())->GetFilename(); - int n_err = error->GetErrorCode(); - - if (n_err != SP_ERROR_NATIVE) - { - g_Logger.LogError("[SM] Plugin encountered error %d: %s", - n_err, - error->GetErrorString()); - } - - if ((lastname=error->GetLastNative(NULL)) != NULL) - { - const char *custerr; - if ((custerr=error->GetCustomErrorString()) != NULL) - { - g_Logger.LogError("[SM] Native \"%s\" reported: %s", lastname, custerr); - } else { - g_Logger.LogError("[SM] Native \"%s\" encountered a generic error.", lastname); - } - } - - if (!error->DebugInfoAvailable()) - { - g_Logger.LogError("[SM] Debug mode is not enabled for \"%s\"", plname); - g_Logger.LogError("[SM] To enable debug mode, edit plugin_settings.cfg, or type: sm plugins debug %d on", - _GetPluginIndex(ctx)); - return; - } - - CallStackInfo stk_info; - int i = 0; - g_Logger.LogError("[SM] Displaying call stack trace for plugin \"%s\":", plname); - while (error->GetTraceInfo(&stk_info)) - { - g_Logger.LogError("[SM] [%d] Line %d, %s::%s()", - i++, - stk_info.line, - stk_info.filename, - stk_info.function); - } -} - int DebugReport::_GetPluginIndex(IPluginContext *ctx) { int id = 1; @@ -199,3 +154,46 @@ int DebugReport::_GetPluginIndex(IPluginContext *ctx) return pluginsys->GetPluginCount() + 1; } +void DebugReport::ReportError(const IErrorReport &report, IFrameIterator &iter) +{ + // Find the nearest plugin to blame. + const char *blame = nullptr; + for (; !iter.Done(); iter.Next()) { + if (iter.IsScriptedFrame()) { + IPlugin *plugin = pluginsys->FindPluginByContext(iter.Context()->GetContext()); + if (plugin) + blame = plugin->GetFilename(); + else + blame = iter.Context()->GetRuntime()->GetFilename(); + break; + } + } + + iter.Reset(); + + g_Logger.LogError("[SM] Exception reported: %s", report.Message()); + if (blame) + g_Logger.LogError("[SM] Blaming plugin: %s", blame); + g_Logger.LogError("[SM] Call stack trace:"); + + for (int index = 0; !iter.Done(); iter.Next(), index++) { + const char *fn = iter.FunctionName(); + if (!fn) + fn = ""; + + if (iter.IsNativeFrame()) { + g_Logger.LogError("[SM] [%d] %s", index, fn); + continue; + } + if (iter.IsScriptedFrame()) { + const char *file = iter.FilePath(); + if (!file) + file = ""; + g_Logger.LogError("[SM] [%d] Line %d, %s::%s()", + index, + iter.LineNumber(), + file, + fn); + } + } +} diff --git a/core/logic/DebugReporter.h b/core/logic/DebugReporter.h index ac4d97a9..e43b3a08 100644 --- a/core/logic/DebugReporter.h +++ b/core/logic/DebugReporter.h @@ -42,7 +42,7 @@ class DebugReport : public: // SMGlobalClass void OnSourceModAllInitialized(); public: // IDebugListener - void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error); + void ReportError(const IErrorReport &report, IFrameIterator &iter); void OnDebugSpew(const char *msg, ...); public: void GenerateError(IPluginContext *ctx, cell_t func_idx, int err, const char *message, ...); diff --git a/core/logic/NativeInvoker.cpp b/core/logic/NativeInvoker.cpp deleted file mode 100644 index 9698bd7a..00000000 --- a/core/logic/NativeInvoker.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/** - * vim: set ts=4 sw=4 tw=99 noet : - * ============================================================================= - * SourcePawn - * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#include "NativeInvoker.h" -#include "ShareSys.h" - -NativeInterface g_NInvoke; - -NativeInvoker::NativeInvoker() -{ -} - -NativeInvoker::~NativeInvoker() -{ -} - -const char *NativeInterface::GetInterfaceName() -{ - return SMINTERFACE_NINVOKE_NAME; -} - -unsigned int NativeInterface::GetInterfaceVersion() -{ - return SMINTERFACE_NINVOKE_VERSION; -} - -void NativeInterface::OnSourceModAllInitialized() -{ - sharesys->AddInterface(NULL, &g_NInvoke); -} - -IPluginRuntime *NativeInterface::CreateRuntime(const char *name, size_t bytes) -{ - return g_pSourcePawn2->CreateEmptyRuntime(name, bytes); -} - -INativeInvoker *NativeInterface::CreateInvoker() -{ - return new NativeInvoker(); -} - -bool NativeInvoker::Start(IPluginContext *pContext, const char *name) -{ - ke::Ref entry = g_ShareSys.FindNative(name); - if (!entry) - return false; - - if (!entry->owner) - return false; - - native_ = entry->func(); - context_ = pContext; - - m_curparam = 0; - m_errorstate = SP_ERROR_NONE; - - return true; -} - -cell_t 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 = *(cell_t *)&number; - - 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 (context_ == NULL) - return; - - m_errorstate = SP_ERROR_NONE; - m_curparam = 0; - context_ = NULL; - native_ = NULL; -} - -int NativeInvoker::SetError(int err) -{ - m_errorstate = err; - return err; -} - -int NativeInvoker::Invoke(cell_t *result) -{ - int err = SP_ERROR_NONE; - - if (context_ == NULL) - return SP_ERROR_INVALID_NATIVE; - - if (m_errorstate != SP_ERROR_NONE) - { - err = m_errorstate; - Cancel(); - return err; - } - - cell_t tresult; - - if (result == NULL) - { - result = &tresult; - } - - //This is for re-entrancy! - IPluginContext *ctx = context_; - 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; - bool docopies = true; - - if (numparams) - { - //Save the info locally, then reset it for re-entrant calls. - memcpy(temp_info, m_info, numparams * sizeof(ParamInfo)); - } - m_curparam = 0; - context_ = NULL; - - /* Initialize 0th parameter */ - _temp_params[0] = numparams; - - /* Browse the parameters and build arrays */ - for (i = 0; i < numparams; i++) - { - /* Is this marked as an array? */ - if (temp_info[i].marked) - { - if (!temp_info[i].str.is_sz) - { - /* Allocate a normal/generic array */ - if ((err = ctx->HeapAlloc(temp_info[i].size, - &temp_info[i].local_addr, - &temp_info[i].phys_addr)) - != SP_ERROR_NONE) - { - 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 */ - if ((err = ctx->HeapAlloc(cells, - &temp_info[i].local_addr, - &temp_info[i].phys_addr)) - != SP_ERROR_NONE) - { - 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) - { - if ((err = ctx->StringToLocalUTF8(temp_info[i].local_addr, - temp_info[i].size, - (const char *)temp_info[i].orig_addr, - NULL)) - != SP_ERROR_NONE) - { - break; - } - } - /* 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 - { - if ((err = ctx->StringToLocal(temp_info[i].local_addr, - temp_info[i].size, - (const char *)temp_info[i].orig_addr)) - != SP_ERROR_NONE) - { - break; - } - } - } - } /* 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 (err == SP_ERROR_NONE) - { - *result = native_(ctx, _temp_params); - if (ctx->GetLastNativeError() != SP_ERROR_NONE) - { - docopies = false; - ctx->ClearLastNativeError(); - } - } - else - { - docopies = false; - } - - /* i should be equal to the last valid parameter + 1 */ - 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 ((err = ctx->HeapPop(temp_info[i].local_addr)) != SP_ERROR_NONE) - { - return err; - } - } - - return err; -} diff --git a/core/logic/NativeInvoker.h b/core/logic/NativeInvoker.h deleted file mode 100644 index 6e673245..00000000 --- a/core/logic/NativeInvoker.h +++ /dev/null @@ -1,102 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#ifndef _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ -#define _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ - -#include -#include -#include "common_logic.h" - -using namespace SourceMod; -using namespace SourcePawn; - -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 INativeInvoker -{ -public: - NativeInvoker(); - ~NativeInvoker(); -public: /* ICallable */ - int PushCell(cell_t cell); - int PushCellByRef(cell_t *cell, int flags=SM_PARAM_COPYBACK); - int PushFloat(float number); - int PushFloatByRef(float *number, int flags=SM_PARAM_COPYBACK); - int PushArray(cell_t *inarray, unsigned int cells, int flags=0); - int PushString(const char *string); - int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags); - void Cancel(); -public: /* INativeInvoker */ - bool Start(IPluginContext *pContext, const char *name); - int Invoke(cell_t *result); -private: - int _PushString(const char *string, int sz_flags, int cp_flags, size_t len); - int SetError(int err); -private: - IPluginContext *context_; - SPVM_NATIVE_FUNC native_; - cell_t m_params[SP_MAX_EXEC_PARAMS]; - ParamInfo m_info[SP_MAX_EXEC_PARAMS]; - unsigned int m_curparam; - int m_errorstate; -}; - -class NativeInterface : - public INativeInterface, - public SMGlobalClass -{ -public: /* SMGlobalClass */ - void OnSourceModAllInitialized(); -public: /* SMInterface */ - unsigned int GetInterfaceVersion(); - const char *GetInterfaceName(); -public: /* INativeInvoker */ - IPluginRuntime *CreateRuntime(const char *name, size_t bytes); - INativeInvoker *CreateInvoker(); -}; - -extern NativeInterface g_NInvoke; - -#endif /* _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ */ diff --git a/core/logic/PluginSys.cpp b/core/logic/PluginSys.cpp index faa740b7..e67daafc 100644 --- a/core/logic/PluginSys.cpp +++ b/core/logic/PluginSys.cpp @@ -1175,13 +1175,9 @@ bool CPluginManager::FindOrRequirePluginDeps(CPlugin *pPlugin, char *error, size if ((pFunc=pBase->GetFunctionByName(buffer))) { cell_t res; - pFunc->Execute(&res); - if (pPlugin->GetBaseContext()->GetLastNativeError() != SP_ERROR_NONE) - { + if (pFunc->Execute(&res) != SP_ERROR_NONE) { if (error) - { smcore.Format(error, maxlength, "Fatal error during initializing plugin load"); - } return false; } } @@ -1303,13 +1299,9 @@ bool CPluginManager::LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass if ((pFunc = pBase->GetFunctionByName(buffer)) != NULL) { cell_t res; - pFunc->Execute(&res); - if (pPlugin->GetBaseContext()->GetLastNativeError() != SP_ERROR_NONE) - { + if (pFunc->Execute(&res) != SP_ERROR_NONE) { if (error) - { smcore.Format(error, maxlength, "Fatal error during plugin initialization (ext req)"); - } return false; } } diff --git a/core/logic/smn_console.cpp b/core/logic/smn_console.cpp index 963f4314..9b0ee6fa 100644 --- a/core/logic/smn_console.cpp +++ b/core/logic/smn_console.cpp @@ -150,11 +150,12 @@ static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; - size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + size_t len; { - return 0; + DetectExceptions eh(pContext); + len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); + if (eh.HasException()) + return 0; } /* One byte for null terminator, one for newline */ @@ -171,11 +172,12 @@ static cell_t sm_InsertServerCommand(IPluginContext *pContext, const cell_t *par g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; - size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + size_t len; { - return 0; + DetectExceptions eh(pContext); + len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); + if (eh.HasException()) + return 0; } /* One byte for null terminator, one for newline */ @@ -211,11 +213,12 @@ static cell_t sm_ClientCommand(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(params[1]); char buffer[256]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + size_t len; { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } engine->ClientCommand(pPlayer->GetEdict(), buffer); @@ -240,11 +243,11 @@ static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(params[1]); char buffer[256]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } engine->FakeClientCommand(pPlayer->GetEdict(), buffer); @@ -258,11 +261,13 @@ static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params) /* Build the format string */ char buffer[1024]; - size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + size_t len; { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 2); + if (eh.HasException()) + return 0; } /* If we're printing to the server, shortcut out */ diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index f6d2ef76..205e9942 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -130,13 +130,14 @@ void LogAction(Handle_t hndl, int type, int client, int target, const char *mess g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); - - if (pContext->GetLastNativeError() == SP_ERROR_NONE) - { - pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", buffer); - } + { + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) + return 0; + } + pContext->ReportError("%s", buffer); return 0; } @@ -388,16 +389,16 @@ static cell_t SetFailState(IPluginContext *pContext, const cell_t *params) { char buffer[2048]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - { - pPlugin->SetErrorState(Plugin_Failed, "%s", str); - return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "Formatting error (%s)", str); - } - else - { + { + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) { + pPlugin->SetErrorState(Plugin_Failed, "%s", str); + return 0; + } pPlugin->SetErrorState(Plugin_Failed, "%s", buffer); - return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", buffer); + pContext->ReportFatalError("%s", buffer); + return 0; } } @@ -507,12 +508,12 @@ static cell_t sm_LogAction(IPluginContext *pContext, const cell_t *params) { char buffer[2048]; g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - { - return 0; - } + { + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3); + if (eh.HasException()) + return 0; + } IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext()); @@ -536,14 +537,15 @@ static cell_t LogToFile(IPluginContext *pContext, const cell_t *params) } char buffer[2048]; - g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - { - fclose(fp); - return 0; - } + { + DetectExceptions eh(pContext); + g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) { + fclose(fp); + return 0; + } + } IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext()); @@ -569,14 +571,15 @@ static cell_t LogToFileEx(IPluginContext *pContext, const cell_t *params) } char buffer[2048]; - g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - { - fclose(fp); - return 0; - } + { + DetectExceptions eh(pContext); + g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) { + fclose(fp); + return 0; + } + } g_Logger.LogToOpenFile(fp, "%s", buffer); @@ -648,21 +651,27 @@ static cell_t RequireFeature(IPluginContext *pContext, const cell_t *params) if (sharesys->TestFeature(pContext->GetRuntime(), type, name) != FeatureStatus_Available) { - char buffer[255]; - char *msg = buffer; - char default_message[255]; + char buffer[255]; + char *msg = buffer; + char default_message[255]; SMPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext()); - - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3); - if (pContext->GetLastNativeError() != SP_ERROR_NONE || buffer[0] == '\0') - { - g_pSM->Format(default_message, sizeof(default_message), "Feature \"%s\" not available", name); - msg = default_message; - } - pPlugin->SetErrorState(Plugin_Error, "%s", msg); - return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", msg); - } - + + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3); + if (eh.HasException()) + buffer[0] = '\0'; + + if (buffer[0] == '\0') { + g_pSM->Format(default_message, sizeof(default_message), "Feature \"%s\" not available", name); + msg = default_message; + } + pPlugin->SetErrorState(Plugin_Error, "%s", msg); + + if (!eh.HasException()) + pContext->ReportFatalError("%s", msg); + return 0; + } + return 1; } diff --git a/core/logic/smn_datapacks.cpp b/core/logic/smn_datapacks.cpp index 2612abbc..9a369dbe 100644 --- a/core/logic/smn_datapacks.cpp +++ b/core/logic/smn_datapacks.cpp @@ -125,7 +125,6 @@ static cell_t smn_WritePackString(IPluginContext *pContext, const cell_t *params HandleError herr; HandleSecurity sec; IDataPack *pDataPack; - int err; sec.pOwner = pContext->GetIdentity(); sec.pIdentity = g_pCoreIdent; @@ -137,12 +136,7 @@ static cell_t smn_WritePackString(IPluginContext *pContext, const cell_t *params } char *str; - if ((err=pContext->LocalToString(params[2], &str)) != SP_ERROR_NONE) - { - pContext->ThrowNativeErrorEx(err, NULL); - return 0; - } - + pContext->LocalToString(params[2], &str); pDataPack->PackString(str); return 1; diff --git a/core/logic/smn_fakenatives.cpp b/core/logic/smn_fakenatives.cpp index f5f9d66a..3d785804 100644 --- a/core/logic/smn_fakenatives.cpp +++ b/core/logic/smn_fakenatives.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet: * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -81,18 +81,12 @@ cell_t FakeNativeRouter(IPluginContext *pContext, const cell_t *params, void *pD s_curparams[i] = params[i]; } - /* Push info and execute. */ + // Push info and execute. If Invoke() fails, the error will propagate up. + // We still carry on below to clear our global state. cell_t result = 0; native->call->PushCell(pCaller->GetMyHandle()); native->call->PushCell(params[0]); - int error; - if ((error=native->call->Execute(&result)) != SP_ERROR_NONE) - { - if (pContext->GetLastNativeError() == SP_ERROR_NONE) - { - pContext->ThrowNativeErrorEx(error, "Error encountered while processing a dynamic native"); - } - } + native->call->Invoke(&result); /* Restore everything from the stack if necessary */ s_curnative = pSaveNative; @@ -141,15 +135,15 @@ static cell_t ThrowNativeError(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[512]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - s_curcaller->ThrowNativeError("Error encountered while processing a dynamic native"); - } else { - s_curcaller->ThrowNativeErrorEx(params[1], "%s", buffer); + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } + pContext->ReportError("%s", buffer); return 0; } @@ -402,36 +396,32 @@ static cell_t FormatNativeString(IPluginContext *pContext, const cell_t *params) char *format_buffer; if (out_param) - { - if ((err=s_curcaller->LocalToString(s_curparams[out_param], &output_buffer)) != SP_ERROR_NONE) - { - return err; - } - } else { + s_curcaller->LocalToString(s_curparams[out_param], &output_buffer); + else pContext->LocalToString(params[6], &output_buffer); - } if (fmt_param) - { - if ((err=s_curcaller->LocalToString(s_curparams[fmt_param], &format_buffer)) != SP_ERROR_NONE) - { - return err; - } - } else { + s_curcaller->LocalToString(s_curparams[fmt_param], &format_buffer); + else pContext->LocalToString(params[7], &format_buffer); - } /* Get maximum length */ size_t maxlen = (size_t)params[4]; /* Do the format */ - size_t written = smcore.atcprintf(output_buffer, maxlen, format_buffer, s_curcaller, s_curparams, &var_param); + size_t written; + { + DetectExceptions eh(pContext); + written = smcore.atcprintf(output_buffer, maxlen, format_buffer, s_curcaller, s_curparams, &var_param); + if (eh.HasException()) + return 0; + } cell_t *addr; pContext->LocalToPhysAddr(params[5], &addr); *addr = (cell_t)written; - return s_curcaller->GetLastNativeError(); + return SP_ERROR_NONE; } //tee hee diff --git a/core/logic/smn_filesystem.cpp b/core/logic/smn_filesystem.cpp index ff1e925f..5f69f74e 100644 --- a/core/logic/smn_filesystem.cpp +++ b/core/logic/smn_filesystem.cpp @@ -765,9 +765,12 @@ static cell_t sm_WriteFileLine(IPluginContext *pContext, const cell_t *params) int arg = 3; char buffer[2048]; - smcore.atcprintf(buffer, sizeof(buffer), fmt, pContext, params, &arg); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - return 0; + { + DetectExceptions eh(pContext); + smcore.atcprintf(buffer, sizeof(buffer), fmt, pContext, params, &arg); + if (eh.HasException()) + return 0; + } if (SystemFile *sysfile = file->AsSystemFile()) { fprintf(sysfile->fp(), "%s\n", buffer); @@ -797,9 +800,12 @@ static cell_t sm_BuildPath(IPluginContext *pContext, const cell_t *params) pContext->LocalToString(params[2], &buffer); pContext->LocalToString(params[4], &fmt); - smcore.atcprintf(path, sizeof(path), fmt, pContext, params, &arg); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - return 0; + { + DetectExceptions eh(pContext); + smcore.atcprintf(path, sizeof(path), fmt, pContext, params, &arg); + if (eh.HasException()) + return 0; + } return g_pSM->BuildPath(Path_SM_Rel, buffer, params[3], "%s", path); } @@ -808,12 +814,13 @@ static cell_t sm_LogToGame(IPluginContext *pContext, const cell_t *params) { g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); + size_t len; char buffer[1024]; - size_t len = g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + len = g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) + return 0; } if (len >= sizeof(buffer)-2) @@ -835,11 +842,11 @@ static cell_t sm_LogMessage(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) + return 0; } IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext()); @@ -853,11 +860,11 @@ static cell_t sm_LogError(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) + return 0; } IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext()); @@ -900,9 +907,12 @@ static cell_t sm_LogToOpenFile(IPluginContext *pContext, const cell_t *params) char buffer[2048]; g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - return 0; + { + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; + } IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext()); g_Logger.LogToOpenFile(sysfile->fp(), "[%s] %s", pPlugin->GetFilename(), buffer); @@ -922,9 +932,12 @@ static cell_t sm_LogToOpenFileEx(IPluginContext *pContext, const cell_t *params) char buffer[2048]; g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) - return 0; + { + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; + } g_Logger.LogToOpenFile(sysfile->fp(), "%s", buffer); return 1; diff --git a/core/logic/smn_functions.cpp b/core/logic/smn_functions.cpp index 82308bf8..4b0ea105 100644 --- a/core/logic/smn_functions.cpp +++ b/core/logic/smn_functions.cpp @@ -564,6 +564,7 @@ static cell_t sm_CallFinish(IPluginContext *pContext, const cell_t *params) pContext->LocalToPhysAddr(params[1], &result); + // Note: Execute() swallows exceptions, so this is okay. if (s_pFunction) { IPluginFunction *pFunction = s_pFunction; diff --git a/core/logic/smn_players.cpp b/core/logic/smn_players.cpp index 96bd262d..8dbb8c91 100644 --- a/core/logic/smn_players.cpp +++ b/core/logic/smn_players.cpp @@ -1087,11 +1087,12 @@ static cell_t _ShowActivity(IPluginContext *pContext, if (replyto == SM_REPLY_CONSOLE) { g_pSM->SetGlobalTarget(client); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s\n", tag, buffer); @@ -1102,11 +1103,12 @@ static cell_t _ShowActivity(IPluginContext *pContext, else { g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s\n", tag, buffer); @@ -1141,11 +1143,12 @@ static cell_t _ShowActivity(IPluginContext *pContext, { newsign = name; } - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer); @@ -1165,11 +1168,12 @@ static cell_t _ShowActivity(IPluginContext *pContext, { newsign = name; } - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer); @@ -1210,11 +1214,11 @@ static cell_t _ShowActivity2(IPluginContext *pContext, } g_pSM->SetGlobalTarget(client); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } /* We don't display directly to the console because the chat text @@ -1227,11 +1231,11 @@ static cell_t _ShowActivity2(IPluginContext *pContext, else { g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s\n", tag, buffer); @@ -1266,11 +1270,12 @@ static cell_t _ShowActivity2(IPluginContext *pContext, { newsign = name; } - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer); @@ -1290,11 +1295,12 @@ static cell_t _ShowActivity2(IPluginContext *pContext, { newsign = name; } - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + if (eh.HasException()) + return 0; } g_pSM->Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer); @@ -1350,11 +1356,11 @@ static cell_t KickClient(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(client); char buffer[256]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } if (pPlayer->IsFakeClient()) @@ -1387,11 +1393,11 @@ static cell_t KickClientEx(IPluginContext *pContext, const cell_t *params) g_pSM->SetGlobalTarget(client); char buffer[256]; - g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } pPlayer->Kick(buffer); diff --git a/core/logic/smn_sorting.cpp b/core/logic/smn_sorting.cpp index 65d0a5db..7d175ee1 100644 --- a/core/logic/smn_sorting.cpp +++ b/core/logic/smn_sorting.cpp @@ -232,6 +232,7 @@ static cell_t sm_SortStrings(IPluginContext *pContext, const cell_t *params) if ((err=pContext->HeapAlloc(array_size, &amx_addr, &phys_addr)) != SP_ERROR_NONE) { pContext->ThrowNativeErrorEx(err, "Ran out of memory to sort"); + return 0; } g_CurStringArray = array; @@ -282,12 +283,16 @@ struct sort_info cell_t array_addr; cell_t *array_base; cell_t *array_remap; + ExceptionHandler *eh; }; sort_info g_SortInfo; int sort1d_amx_custom(const void *elem1, const void *elem2) { + if (g_SortInfo.eh->HasException()) + return 0; + cell_t c1 = *(cell_t *)elem1; cell_t c2 = *(cell_t *)elem2; @@ -297,7 +302,7 @@ int sort1d_amx_custom(const void *elem1, const void *elem2) pf->PushCell(c2); pf->PushCell(g_SortInfo.array_addr); pf->PushCell(g_SortInfo.hndl); - pf->Execute(&result); + pf->Invoke(&result); return result; } @@ -317,22 +322,25 @@ static cell_t sm_SortCustom1D(IPluginContext *pContext, const cell_t *params) sort_info oldinfo = g_SortInfo; - + DetectExceptions eh(pContext); g_SortInfo.hndl = params[4]; g_SortInfo.array_addr = params[1]; g_SortInfo.array_remap = NULL; g_SortInfo.array_base = NULL; g_SortInfo.pFunc = pFunction; + g_SortInfo.eh = &eh; qsort(array, array_size, sizeof(cell_t), sort1d_amx_custom); g_SortInfo = oldinfo; - return 1; } int sort2d_amx_custom(const void *elem1, const void *elem2) { + if (g_SortInfo.eh->HasException()) + return 0; + cell_t c1 = *(cell_t *)elem1; cell_t c2 = *(cell_t *)elem2; @@ -349,7 +357,7 @@ int sort2d_amx_custom(const void *elem1, const void *elem2) g_SortInfo.pFunc->PushCell(c2_addr); g_SortInfo.pFunc->PushCell(g_SortInfo.array_addr); g_SortInfo.pFunc->PushCell(g_SortInfo.hndl); - g_SortInfo.pFunc->Execute(&result); + g_SortInfo.pFunc->Invoke(&result); return result; } @@ -378,9 +386,11 @@ static cell_t sm_SortCustom2D(IPluginContext *pContext, const cell_t *params) sort_info oldinfo = g_SortInfo; + DetectExceptions eh(pContext); g_SortInfo.pFunc = pFunction; g_SortInfo.hndl = params[4]; g_SortInfo.array_addr = params[1]; + g_SortInfo.eh = &eh; /** Same process as in strings, back up the old indices for later fixup */ g_SortInfo.array_base = array; @@ -511,19 +521,23 @@ struct sort_infoADT cell_t array_bsize; Handle_t array_hndl; Handle_t hndl; + ExceptionHandler *eh; }; sort_infoADT g_SortInfoADT; int sort_adtarray_custom(const void *elem1, const void *elem2) { + if (g_SortInfoADT.eh->HasException()) + return 0; + cell_t result = 0; IPluginFunction *pf = g_SortInfoADT.pFunc; pf->PushCell(((cell_t) ((cell_t *) elem1 - g_SortInfoADT.array_base)) / g_SortInfoADT.array_bsize); pf->PushCell(((cell_t) ((cell_t *) elem2 - g_SortInfoADT.array_base)) / g_SortInfoADT.array_bsize); pf->PushCell(g_SortInfoADT.array_hndl); pf->PushCell(g_SortInfoADT.hndl); - pf->Execute(&result); + pf->Invoke(&result); return result; } @@ -552,11 +566,13 @@ static cell_t sm_SortADTArrayCustom(IPluginContext *pContext, const cell_t *para sort_infoADT oldinfo = g_SortInfoADT; + DetectExceptions eh(pContext); g_SortInfoADT.pFunc = pFunction; g_SortInfoADT.array_base = array; g_SortInfoADT.array_bsize = (cell_t) blocksize; g_SortInfoADT.array_hndl = params[1]; g_SortInfoADT.hndl = params[3]; + g_SortInfoADT.eh = &eh; qsort(array, arraysize, blocksize * sizeof(cell_t), sort_adtarray_custom); diff --git a/core/sm_stringutil.cpp b/core/sm_stringutil.cpp index 6babf358..a3ec8c83 100644 --- a/core/sm_stringutil.cpp +++ b/core/sm_stringutil.cpp @@ -1180,12 +1180,7 @@ reswitch: { CHECK_ARGS(0); char *str; - int err; - if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, "Could not deference string"); - return 0; - } + pCtx->LocalToString(params[arg], &str); AddString(&buf_p, llen, str, width, prec); arg++; break; diff --git a/core/smn_bitbuffer.cpp b/core/smn_bitbuffer.cpp index dfcb5f1c..ac02564d 100644 --- a/core/smn_bitbuffer.cpp +++ b/core/smn_bitbuffer.cpp @@ -194,7 +194,6 @@ static cell_t smn_BfWriteString(IPluginContext *pCtx, const cell_t *params) HandleError herr; HandleSecurity sec; bf_write *pBitBuf; - int err; sec.pOwner = NULL; sec.pIdentity = g_pCoreIdent; @@ -206,11 +205,7 @@ static cell_t smn_BfWriteString(IPluginContext *pCtx, const cell_t *params) } char *str; - if ((err=pCtx->LocalToString(params[2], &str)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToString(params[2], &str); pBitBuf->WriteString(str); diff --git a/core/smn_console.cpp b/core/smn_console.cpp index 6d7e0b96..f4cece35 100644 --- a/core/smn_console.cpp +++ b/core/smn_console.cpp @@ -909,11 +909,12 @@ static cell_t sm_ServerCommandEx(IPluginContext *pContext, const cell_t *params) g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; - size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 3); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + size_t len; { - return 0; + DetectExceptions eh(pContext); + len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 3); + if (eh.HasException()) + return 0; } /* One byte for null terminator, one for newline */ @@ -965,11 +966,11 @@ static cell_t FakeClientCommandEx(IPluginContext *pContext, const cell_t *params g_SourceMod.SetGlobalTarget(params[1]); char buffer[256]; - g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); - - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } g_HL2.AddToFakeCliCmdQueue(params[1], GetPlayerUserId(pPlayer->GetEdict()), buffer); diff --git a/core/smn_halflife.cpp b/core/smn_halflife.cpp index c49c6431..d8e02507 100644 --- a/core/smn_halflife.cpp +++ b/core/smn_halflife.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod * Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. @@ -321,12 +321,12 @@ static cell_t PrintToChat(IPluginContext *pContext, const cell_t *params) g_SourceMod.SetGlobalTarget(client); char buffer[192]; - g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); - /* Check for an error before printing to the client */ - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } if (!g_HL2.TextMsg(client, HUD_PRINTTALK, buffer)) @@ -355,12 +355,12 @@ static cell_t PrintCenterText(IPluginContext *pContext, const cell_t *params) g_SourceMod.SetGlobalTarget(client); char buffer[192]; - g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); - - /* Check for an error before printing to the client */ - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + { - return 0; + DetectExceptions eh(pContext); + g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } if (!g_HL2.TextMsg(client, HUD_PRINTCENTER, buffer)) @@ -389,12 +389,11 @@ static cell_t PrintHintText(IPluginContext *pContext, const cell_t *params) g_SourceMod.SetGlobalTarget(client); char buffer[192]; - g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); - - /* Check for an error before printing to the client */ - if (pContext->GetLastNativeError() != SP_ERROR_NONE) { - return 0; + DetectExceptions eh(pContext); + g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); + if (eh.HasException()) + return 0; } if (!g_HL2.HintTextMsg(client, buffer)) diff --git a/core/smn_hudtext.cpp b/core/smn_hudtext.cpp index d1188e51..1a1a2987 100644 --- a/core/smn_hudtext.cpp +++ b/core/smn_hudtext.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet: * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -415,10 +415,12 @@ static cell_t ShowSyncHudText(IPluginContext *pContext, const cell_t *params) } g_SourceMod.SetGlobalTarget(client); - g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + { - return 0; + DetectExceptions eh(pContext); + g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3); + if (eh.HasException()) + return 0; } g_hud_params.channel = s_HudMsgHelpers.AutoSelectChannel(client, obj); @@ -488,10 +490,12 @@ static cell_t ShowHudText(IPluginContext *pContext, const cell_t *params) } g_SourceMod.SetGlobalTarget(client); - g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3); - if (pContext->GetLastNativeError() != SP_ERROR_NONE) + { - return 0; + DetectExceptions eh(pContext); + g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3); + if (eh.HasException()) + return 0; } if (params[2] == -1) diff --git a/core/smn_protobuf.cpp b/core/smn_protobuf.cpp index 2485aabe..5552e16f 100644 --- a/core/smn_protobuf.cpp +++ b/core/smn_protobuf.cpp @@ -52,13 +52,8 @@ // Assumes message field name is param 2, gets as strField #define GET_FIELD_NAME_OR_ERR() \ - int err; \ char *strField; \ - if ((err=pCtx->LocalToString(params[2], &strField)) != SP_ERROR_NONE) \ - { \ - pCtx->ThrowNativeErrorEx(err, NULL); \ - return 0; \ - } + pCtx->LocalToString(params[2], &strField); static cell_t smn_PbReadInt(IPluginContext *pCtx, const cell_t *params) { @@ -387,11 +382,7 @@ static cell_t smn_PbSetString(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); char *strValue; - if ((err=pCtx->LocalToString(params[3], &strValue)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToString(params[3], &strValue); int index = params[0] >= 4 ? params[4] : -1; if (index < 0) @@ -418,11 +409,7 @@ static cell_t smn_PbSetColor(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *clrParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &clrParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &clrParams); Color clr( clrParams[0], @@ -455,11 +442,7 @@ static cell_t smn_PbSetAngle(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *angParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &angParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &angParams); QAngle ang( sp_ctof(angParams[0]), @@ -491,11 +474,7 @@ static cell_t smn_PbSetVector(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *vecParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &vecParams); Vector vec( sp_ctof(vecParams[0]), @@ -527,11 +506,7 @@ static cell_t smn_PbSetVector2D(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *vecParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &vecParams); Vector2D vec( sp_ctof(vecParams[0]), @@ -602,11 +577,7 @@ static cell_t smn_PbAddString(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); char *strValue; - if ((err=pCtx->LocalToString(params[3], &strValue)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToString(params[3], &strValue); if (!msg->AddString(strField, strValue)) { @@ -622,11 +593,7 @@ static cell_t smn_PbAddColor(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *clrParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &clrParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &clrParams); Color clr( clrParams[0], @@ -648,11 +615,7 @@ static cell_t smn_PbAddAngle(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *angParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &angParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &angParams); QAngle ang( sp_ctof(angParams[0]), @@ -673,11 +636,7 @@ static cell_t smn_PbAddVector(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *vecParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &vecParams); Vector vec( sp_ctof(vecParams[0]), @@ -698,11 +657,7 @@ static cell_t smn_PbAddVector2D(IPluginContext *pCtx, const cell_t *params) GET_FIELD_NAME_OR_ERR(); cell_t *vecParams; - if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) - { - pCtx->ThrowNativeErrorEx(err, NULL); - return 0; - } + pCtx->LocalToPhysAddr(params[3], &vecParams); Vector2D vec( sp_ctof(vecParams[0]), diff --git a/extensions/sdktools/vdecoder.cpp b/extensions/sdktools/vdecoder.cpp index f4dc038d..8bac6555 100644 --- a/extensions/sdktools/vdecoder.cpp +++ b/extensions/sdktools/vdecoder.cpp @@ -290,8 +290,7 @@ DataStatus DecodeValveParam(IPluginContext *pContext, case Valve_Vector: { cell_t *addr; - int err; - err = pContext->LocalToPhysAddr(param, &addr); + pContext->LocalToPhysAddr(param, &addr); unsigned char *mem = (unsigned char *)buffer; if (data->type == PassType_Basic) @@ -317,12 +316,6 @@ DataStatus DecodeValveParam(IPluginContext *pContext, } } - if (err != SP_ERROR_NONE) - { - pContext->ThrowNativeErrorEx(err, "Could not read plugin data"); - return Data_Fail; - } - /* Use placement new to initialize the object cleanly * This has no destructor so we don't need to do * DestroyValveParam() or something :] diff --git a/public/INativeInvoker.h b/public/INativeInvoker.h deleted file mode 100644 index 91429308..00000000 --- a/public/INativeInvoker.h +++ /dev/null @@ -1,107 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#ifndef _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_ -#define _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_ - -/** - * @file INativeInvoker.h - * @brief Interface for invoking natives. - */ - -#include -#include - -#define SMINTERFACE_NINVOKE_NAME "INativeInterface" -#define SMINTERFACE_NINVOKE_VERSION 1 - -#define NINVOKE_DEFAULT_MEMORY 16384 - -namespace SourceMod -{ - class INativeInvoker : public SourcePawn::ICallable - { - public: - /** - * @brief Virtual destructor - use delete to free this. - */ - virtual ~INativeInvoker() - { - } - public: - /** - * @brief Begins a native call. - * - * During a call's preparation, no new calls may be started. - * - * @param pContext Context to invoke native under. - * @param name Name of native. - * @return True if native was found, false otherwise. - */ - virtual bool Start(SourcePawn::IPluginContext *pContext, const char *name) = 0; - - /** - * @brief Invokes the native. The preparation state is cleared immediately, meaning that - * this object can be re-used after or even from inside the native being called. - * - * @param result Optional pointer to retrieve a result. - * @return SP_ERROR return code. - */ - virtual int Invoke(cell_t *result) = 0; - }; - - /** - * @brief Factory for dealing with native invocations. - */ - class INativeInterface : public SMInterface - { - public: - /** - * @brief Creates a virtual plugin. This can be used as an environment to invoke natives. - * - * IPluginRuntime objects must be freed with the delete operator. - * - * @param name Name, or NULL for anonymous. - * @param bytes Number of bytes for memory (NINVOKE_DEFAULT_MEMORY recommended). - * @return New runtime, or NULL on failure. - */ - virtual SourcePawn::IPluginRuntime *CreateRuntime(const char *name, size_t bytes) = 0; - - /** - * @brief Creates an object that can be used to invoke a single native code. - * - * @return New native invoker (free with delete). - */ - virtual INativeInvoker *CreateInvoker() = 0; - }; -} - -#endif /* _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_ */ diff --git a/public/amtl/am-fixedarray.h b/public/amtl/am-fixedarray.h index 4681651d..1925a780 100644 --- a/public/amtl/am-fixedarray.h +++ b/public/amtl/am-fixedarray.h @@ -85,6 +85,10 @@ class FixedArray : public AllocPolicy data_[index] = t; } + T *buffer() const { + return data_; + } + private: FixedArray(const FixedArray &other) KE_DELETE; FixedArray &operator =(const FixedArray &other) KE_DELETE; diff --git a/public/jit/assembler.h b/public/jit/assembler.h index c156cd00..8e82e744 100644 --- a/public/jit/assembler.h +++ b/public/jit/assembler.h @@ -230,13 +230,24 @@ class Label assert(this->offset() == offset); } - private: + protected: // Note that 0 as an invalid offset is okay, because the offset we save for // pending jumps are after the jump opcode itself, and therefore 0 is never // valid, since there are no 0-byte jumps. uint32_t status_; }; +// Label that suppresses its assert, for non-stack use. +class SilentLabel : public Label +{ + public: + SilentLabel() + {} + ~SilentLabel() { + status_ = 0; + } +}; + // A DataLabel is a special form of Label intended for absolute addresses that // are within the code buffer, and thus aren't known yet, and will be // automatically fixed up when calling emitToExecutableMemory(). diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index f1616262..39f52b94 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -1,4 +1,4 @@ -// vim: set sts=2 ts=8 sw=2 tw=99 et: +// vim: set ts=4 sw=4 tw=99 noet: // // Copyright (C) 2006-2015 AlliedModders LLC // @@ -19,15 +19,19 @@ */ #include +#include #include "sp_vm_types.h" /** SourcePawn Engine API Versions */ -#define SOURCEPAWN_ENGINE2_API_VERSION 9 -#define SOURCEPAWN_API_VERSION 0x0209 +#define SOURCEPAWN_ENGINE2_API_VERSION 0xA +#define SOURCEPAWN_API_VERSION 0x020A namespace SourceMod { struct IdentityToken_t; }; +namespace sp { + class Environment; +}; struct sp_context_s; typedef struct sp_context_s sp_context_t; @@ -36,6 +40,8 @@ namespace SourcePawn { class IVirtualMachine; class IPluginRuntime; + class ISourcePawnEngine2; + class ISourcePawnEnvironment; /* Parameter flags */ #define SM_PARAM_COPYBACK (1<<0) /**< Copy an array/reference back after call */ @@ -162,21 +168,23 @@ namespace SourcePawn * @brief Executes the function, resets the pushed parameter list, and * performs any copybacks. * + * The exception state is reset upon entering and leaving this + * function. Callers that want to propagate exceptions from Execute() + * should use Invoke(). ReportError is not preferred since it would + * lose any custom exception messages. + * * @param result Pointer to store return value in. * @return Error code, if any. */ virtual int Execute(cell_t *result) =0; /** - * @brief Executes the function with the given parameter array. - * Parameters are read in forward order (i.e. index 0 is parameter #1) - * NOTE: You will get an error if you attempt to use CallFunction() with - * previously pushed parameters. + * @brief This function is deprecated. If invoked, it reports an error. * - * @param params Array of cell parameters. - * @param num_params Number of parameters to push. - * @param result Pointer to store result of function on return. - * @return SourcePawn error code (if any). + * @param params Unused. + * @param num_params Unused. + * @param result Unused. + * @return SP_ERROR_ABORTED. */ virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) =0; @@ -204,30 +212,22 @@ namespace SourcePawn virtual funcid_t GetFunctionID() =0; /** - * @brief Executes the forward, resets the pushed parameter list, and - * performs any copybacks. + * @brief This function is deprecated. If invoked, it reports an error. * - * Note: A function can only be executed given a runtime it was created in. - * - * @param ctx Context to execute the function in. - * @param result Pointer to store return value in. - * @return Error code, if any. + * @param ctx Unused. + * @param result Unused. + * @return SP_ERROR_ABORTED. */ virtual int Execute2(IPluginContext *ctx, cell_t *result) =0; /** - * @brief Executes the function with the given parameter array. - * Parameters are read in forward order (i.e. index 0 is parameter #1) - * NOTE: You will get an error if you attempt to use CallFunction() with - * previously pushed parameters. + * @brief This function is deprecated. If invoked, it reports an error. * - * Note: A function can only be executed given a runtime it was created in. - * - * @param ctx Context to execute the function in. - * @param params Array of cell parameters. - * @param num_params Number of parameters to push. - * @param result Pointer to store result of function on return. - * @return SourcePawn error code (if any). + * @param ctx Unused. + * @param params Unused. + * @param num_params Unused. + * @param result Unused. + * @return SP_ERROR_ABORTED. */ virtual int CallFunction2(IPluginContext *ctx, const cell_t *params, @@ -240,6 +240,20 @@ namespace SourcePawn * @return IPluginRuntime pointer. */ virtual IPluginRuntime *GetParentRuntime() =0; + + /** + * @brief Executes the function, resets the pushed parameter list, and + * performs any copybacks. + * + * Unlike Execute(), this does not reset the exception state. It is + * illegal to call Invoke() while an exception is unhandled. If it + * returns false, an exception has been thrown, and must either be + * handled via ExceptionHandler or propagated to its caller. + * + * @param result Pointer to store return value in. + * @return True on success, false on error. + */ + virtual bool Invoke(cell_t *rval = nullptr) = 0; }; @@ -309,7 +323,7 @@ namespace SourcePawn * * @param index Unused. * @param native Unused. - * @return Returns SP_ERROR_PARAM. + * @return Returns SP_ERROR_PARAM. */ virtual int GetNativeByIndex(uint32_t index, sp_native_t **native) =0; @@ -409,7 +423,7 @@ namespace SourcePawn /** * @brief If |co| is non-NULL, destroys |co|. No other action is taken. * - * @return Returns SP_ERROR_NONE. + * @return Returns SP_ERROR_NONE. */ virtual int ApplyCompilationOptions(ICompilation *co) =0; @@ -448,13 +462,13 @@ namespace SourcePawn */ virtual unsigned char *GetDataHash() =0; - /** - * @brief Update the native binding at the given index. - * - * @param pfn Native function pointer. - * @param flags Native flags. - * @param user User data pointer. - */ + /** + * @brief Update the native binding at the given index. + * + * @param pfn Native function pointer. + * @param flags Native flags. + * @param user User data pointer. + */ virtual int UpdateNativeBinding(uint32_t index, SPVM_NATIVE_FUNC pfn, uint32_t flags, void *data) = 0; /** @@ -464,6 +478,11 @@ namespace SourcePawn * @return Native pointer, or NULL on failure. */ virtual const sp_native_t *GetNative(uint32_t index) = 0; + + /** + * @brief Return the file or location this plugin was loaded from. + */ + virtual const char *GetFilename() = 0; }; /** @@ -559,7 +578,7 @@ namespace SourcePawn * * @param index Unused. * @param native Unused. - * @return Returns SP_ERROR_PARAM. + * @return Returns SP_ERROR_PARAM. */ virtual int GetNativeByIndex(uint32_t index, sp_native_t **native) =0; @@ -734,6 +753,8 @@ namespace SourcePawn /** * @brief Throws a error and halts any current execution. * + * This function is deprecated. Use ReportError() instead. + * * @param error The error number to set. * @param msg Custom error message format. NULL to use default. * @param ... Message format arguments, if any. @@ -744,6 +765,8 @@ namespace SourcePawn /** * @brief Throws a generic native error and halts any current execution. * + * This function is deprecated. Use ReportError() instead. + * * @param msg Custom error message format. NULL to set no message. * @param ... Message format arguments, if any. * @return 0 for convenience. @@ -816,14 +839,13 @@ namespace SourcePawn virtual IPluginRuntime *GetRuntime() =0; /** - * @brief Executes a function in the context. The function must be - * a member of the context's parent runtime. + * @brief This function is deprecated. If invoked, it reports an error. * - * @param function Function. - * @param params Parameters. - * @param num_params Number of parameters in the parameter array. - * @param result Optional pointer to store the result on success. - * @return Error code. + * @param function Unused. + * @param params Unused. + * @param num_params Unused. + * @param result Unused. + * @return SP_ERROR_ABORTED. */ virtual int Execute2(IPluginFunction *function, const cell_t *params, @@ -832,9 +854,12 @@ namespace SourcePawn /** * @brief Returns whether a context is in an error state. + * + * This function is deprecated. Use DetectExceptions instead. * * This should only be used inside natives to determine whether - * a prior call failed. + * a prior call failed. The return value should only be compared + * against SP_ERROR_NONE. */ virtual int GetLastNativeError() =0; @@ -870,81 +895,171 @@ namespace SourcePawn virtual bool GetKey(int k, void **value) =0; /** - * @brief Clears the last native error. + * @brief If an exception is pending, this removes the exception. It + * is deprecated and should not be used. */ virtual void ClearLastNativeError() =0; - }; + /** + * @brief Return a pointer to the ISourcePawnEngine2 that is active. + * This is a convenience function. + * + * @return API pointer. + */ + virtual ISourcePawnEngine2 *APIv2() = 0; - /** - * @brief Information about a position in a call stack. - */ - struct CallStackInfo - { - const char *filename; /**< NULL if not found */ - unsigned int line; /**< 0 if not found */ - const char *function; /**< NULL if not found */ + /** + * @brief Report an error. + * + * @param message Error message format. + * @param ... Formatting arguments. + */ + virtual void ReportError(const char *fmt, ...) = 0; + + /** + * @brief Report an error with variadic arguments. + * + * @param message Error message format. + * @param ap Formatting arguments. + */ + virtual void ReportErrorVA(const char *fmt, va_list ap) = 0; + + /** + * @brief Report a fatal error. Fatal errors cannot be caught by any + * exception handler. + * + * @param message Error message format. + * @param ... Formatting arguments. + */ + virtual void ReportFatalError(const char *fmt, ...) = 0; + + /** + * @brief Report a fatal error with variadic arguments. Fatal errors + * cannot be caught by any exception handler. + * + * @param message Error message format. + * @param ap Formatting arguments. + */ + virtual void ReportFatalErrorVA(const char *fmt, va_list ap) = 0; + + /** + * @brief Report an error by its builtin number. + * + * @param number Error number. + */ + virtual void ReportErrorNumber(int error) = 0; }; /** - * @brief Retrieves error information from a debug hook. + * @brief Removed. */ - class IContextTrace + class IContextTrace; + + /** + * @brief Information about a reported error. + */ + class IErrorReport { public: /** - * @brief Returns the integer error code. + * @brief Return the message of the error report. * - * @return Integer error code. + * @return Message string. */ - virtual int GetErrorCode() =0; + virtual const char *Message() const = 0; /** - * @brief Returns a string describing the error. + * @brief True if the error is fatal and cannot be handled (though + * reporting can be suppressed). * - * @return Error string. + * @return True if fatal, false otherwise. */ - virtual const char *GetErrorString() =0; + virtual bool IsFatal() const = 0; /** - * @brief Returns whether debug info is available. + * @brief Return the plugin context that caused the error. * - * @return True if debug info is available, false otherwise. + * @return Plugin context. */ - virtual bool DebugInfoAvailable() =0; - - /** - * @brief Returns a custom error message. - * - * @return A pointer to a custom error message, or NULL otherwise. - */ - virtual const char *GetCustomErrorString() =0; - - /** - * @brief Returns trace info for a specific point in the backtrace, if any. - * The next subsequent call to GetTraceInfo() will return the next item in the call stack. - * Calls are retrieved in descending order (i.e. the first item is at the top of the stack/call sequence). - * - * @param trace An ErrorTraceInfo buffer to store information (NULL to ignore). - * @return True if successful, false if there are no more traces. - */ - virtual bool GetTraceInfo(CallStackInfo *trace) =0; - - /** - * @brief Resets the trace to its original position (the call on the top of the stack). - */ - virtual void ResetTrace() =0; - - /** - * @brief Retrieves the name of the last native called. - * Returns NULL if there was no native that caused the error. - * - * @param index Optional pointer to store index. - * @return Native name, or NULL if none. - */ - virtual const char *GetLastNative(uint32_t *index) =0; + virtual IPluginContext *Context() const = 0; }; + /** + * @brief Allows inspecting the stack frames of the SourcePawn environment. + * + * Invoking VM functions while iterating frames will cause the iterator + * to become corrupt. + * + * Frames iterate in most-recent to least-recent order. + */ + class IFrameIterator + { + public: + /** + * @brief Returns whether or not there are more frames to read. + * + * @return True if there are more frames to read, false otherwise. + */ + virtual bool Done() const = 0; + + /** + * @brief Advances to the next frame. + * + * Note that the iterator starts at either a valid frame or no frame. + */ + virtual void Next() = 0; + + /** + * @brief Resets the iterator to the top of the stack. + */ + virtual void Reset() = 0; + + /** + * @brief Returns the context owning the current frame, if any. + * + * @return Context, or null. + */ + virtual IPluginContext *Context() const = 0; + + /** + * @brief Returns whether or not the current frame is a native frame. If it + * is, line numbers and file paths are not available. + * + * @return True if a native frame, false otherwise. + */ + virtual bool IsNativeFrame() const = 0; + + /** + * @brief Returns true if the frame is a scripted frame. + * + * @return True if a scripted frame, false otherwise. + */ + virtual bool IsScriptedFrame() const = 0; + + /** + * @brief Returns the line number of the current frame, or 0 if none is + * available. + * + * @return Line number on success, 0 on failure. + */ + virtual unsigned LineNumber() const = 0; + + /** + * @brief Returns the function name of the current frame, or null if + * none could be computed. + * + * @return Function name on success, null on failure. + */ + virtual const char *FunctionName() const = 0; + + /** + * @brief Returns the file path of the function of the current frame, + * or none could be computed. + * + * @return File path on success, null on failure. + */ + virtual const char *FilePath() const = 0; + }; /** * @brief Provides callbacks for debug information. @@ -953,12 +1068,13 @@ namespace SourcePawn { public: /** - * @brief Invoked on a context execution error. + * @brief No longer invoked. * - * @param ctx Context. - * @param error Object holding error information and a backtrace. + * @param ctx Unused. + * @param error Unused. */ - virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) =0; + virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) + {} /** * @brief Called on debug spew. @@ -967,6 +1083,15 @@ namespace SourcePawn * @param fmt Message formatting arguments (printf-style). */ virtual void OnDebugSpew(const char *msg, ...) =0; + + /** + * @brief Called when an error is reported and no exception + * handler was available. + * + * @param report Error report object. + * @param iter Stack frame iterator. + */ + virtual void ReportError(const IErrorReport &report, IFrameIterator &iter) = 0; }; /** @@ -983,21 +1108,21 @@ namespace SourcePawn /** * @brief Return the name of the profiling tool. * - * @return Profiling tool name. + * @return Profiling tool name. */ virtual const char *Name() = 0; /** * @brief Description of the profiler. * - * @return Description. + * @return Description. */ virtual const char *Description() = 0; /** * @brief Called to render help text. * - * @param render Function to render one line of text. + * @param render Function to render one line of text. */ virtual void RenderHelp(void (*render)(const char *fmt, ...)) = 0; @@ -1013,7 +1138,7 @@ namespace SourcePawn /** * @brief Initiate a stop command. * - * @param render Function to render any help messages. + * @param render Function to render any help messages. */ virtual void Stop(void (*render)(const char *fmt, ...)) = 0; @@ -1029,14 +1154,14 @@ namespace SourcePawn /** * @brief Returns whether or not the profiler is currently profiling. * - * @return True if active, false otherwise. + * @return True if active, false otherwise. */ virtual bool IsActive() = 0; /** * @brief Returns whether the profiler is attached. * - * @return True if attached, false otherwise. + * @return True if attached, false otherwise. */ virtual bool IsAttached() = 0; @@ -1045,8 +1170,8 @@ namespace SourcePawn * * LeaveScope() mus be called exactly once for each call to EnterScope(). * - * @param group A named budget group, or NULL for the default. - * @param name Event name. + * @param group A named budget group, or NULL for the default. + * @param name Event name. */ virtual void EnterScope(const char *group, const char *name) = 0; @@ -1114,10 +1239,10 @@ namespace SourcePawn virtual void ExecFree(void *address) =0; /** - * @brief Sets the debug listener. This should only be called once. - * If called successively (using manual chaining), only the last function should - * attempt to call back into the same plugin. Otherwise, globally cached states - * can be accidentally overwritten. + * @brief Sets the debug listener. + * + * This should be called once on application startup. It is + * not considered part of the userland API and may change at any time. * * @param listener Pointer to an IDebugListener. * @return Old IDebugListener, or NULL if none. @@ -1166,6 +1291,8 @@ namespace SourcePawn virtual void FreePageMemory(void *ptr) =0; }; + class ExceptionHandler; + /** * @brief Outlines the interface a Virtual Machine (JIT) must expose */ @@ -1228,10 +1355,10 @@ namespace SourcePawn virtual void DestroyFakeNative(SPVM_NATIVE_FUNC func) =0; /** - * @brief Sets the debug listener. This should only be called once. - * If called successively (using manual chaining), only the last function should - * attempt to call back into the same plugin. Otherwise, globally cached states - * can be accidentally overwritten. + * @brief Sets the debug listener. + * + * This should be called once on application startup. It is + * not considered part of the userland API and may change at any time. * * @param listener Pointer to an IDebugListener. * @return Old IDebugListener, or NULL if none. @@ -1248,6 +1375,9 @@ namespace SourcePawn /** * @brief Returns the string representation of an error message. * + * This function is deprecated and should not be used. The exception + * handling API should be used instead. + * * @param err Error code. * @return Error string, or NULL if not found. */ @@ -1326,6 +1456,11 @@ namespace SourcePawn * @return New runtime pointer, or NULL on failure. */ virtual IPluginRuntime *LoadBinaryFromFile(const char *file, char *error, size_t maxlength) = 0; + + /** + * @brief Returns the environment. + */ + virtual ISourcePawnEnvironment *Environment() = 0; }; // @brief This class is the v3 API for SourcePawn. It provides access to @@ -1351,6 +1486,23 @@ namespace SourcePawn // all plugin memory. This should not be called while plugins have // active code running on the stack. virtual void Shutdown() = 0; + + // @brief Enters an exception handling scope. This is intended to be + // used on the stack and must have a corresponding call to + // LeaveExceptionHandlingScope. When in an exception handling scope, + // exceptions are not immediately reported. Instead the caller is + // responsible for propagation them or clearing them. + virtual void EnterExceptionHandlingScope(ExceptionHandler *handler) = 0; + + // @brief Leaves the most recent exception handling scope. The handler + // is provided as a sanity check. + virtual void LeaveExceptionHandlingScope(ExceptionHandler *handler) = 0; + + // @brief Returns whether or not an exception is currently pending. + virtual bool HasPendingException(const ExceptionHandler *handler) = 0; + + // @brief Returns the message of the pending exception. + virtual const char *GetPendingExceptionMessage(const ExceptionHandler *handler) = 0; }; // @brief This class is the entry-point to using SourcePawn from a DLL. @@ -1371,6 +1523,96 @@ namespace SourcePawn // @brief A function named "GetSourcePawnFactory" is exported from the // SourcePawn DLL, conforming to the following signature: typedef ISourcePawnFactory *(*GetSourcePawnFactoryFn)(int apiVersion); + + // @brief A helper class for handling exceptions. + // + // ExceptionHandlers can be used to detect, catch, and re-throw VM errors + // within C++ code. + // + // When throwing errors, SourcePawn creates an exception object. The + // exception object is global state. As long as an exception is present, + // all scripted code should immediately abort and return to their callers, + // all native code should exit, all code should propagate any error states + // until the exception is handled. + // + // In some cases, an error code is not available. For example, if a native + // detects an exception, it does not have an error status to propagate. It + // should simply return instead. The return value will be ignored; the VM + // knows to abort the script. + class ExceptionHandler + { + friend class sp::Environment; + + public: + ExceptionHandler(ISourcePawnEngine2 *api) + : env_(api->Environment()), + catch_(true) + { + env_->EnterExceptionHandlingScope(this); + } + ExceptionHandler(IPluginContext *ctx) + : env_(ctx->APIv2()->Environment()), + catch_(true) + { + env_->EnterExceptionHandlingScope(this); + } + ~ExceptionHandler() + { + env_->LeaveExceptionHandlingScope(this); + } + + virtual uint32_t ApiVersion() const { + return SOURCEPAWN_API_VERSION; + } + + // Propagates the exception instead of catching it. After calling this, + // no more SourcePawn code should be executed until the exception is + // handled. Callers should return immediately. + void Rethrow() { + assert(catch_ && HasException()); + catch_ = false; + } + + bool HasException() const { + return env_->HasPendingException(this); + } + + const char *Message() const { + return env_->GetPendingExceptionMessage(this); + } + + private: + // Don't allow heap construction. + ExceptionHandler(const ExceptionHandler &other); + void operator =(const ExceptionHandler &other); + void *operator new(size_t size); + void operator delete(void *, size_t); + + private: + ISourcePawnEnvironment *env_; + ExceptionHandler *next_; + + protected: + // If true, the exception will be swallowed. + bool catch_; + }; + + // @brief An implementation of ExceptionHandler that simply collects + // whether an exception was thrown. + class DetectExceptions : public ExceptionHandler + { + public: + DetectExceptions(ISourcePawnEngine2 *api) + : ExceptionHandler(api) + { + catch_ = false; + } + DetectExceptions(IPluginContext *ctx) + : ExceptionHandler(ctx) + { + catch_ = false; + } + }; }; #endif //_INCLUDE_SOURCEPAWN_VM_API_H_ diff --git a/public/sourcepawn/sp_vm_types.h b/public/sourcepawn/sp_vm_types.h index 4dcfe4c1..f637fcff 100644 --- a/public/sourcepawn/sp_vm_types.h +++ b/public/sourcepawn/sp_vm_types.h @@ -88,6 +88,9 @@ typedef uint32_t funcid_t; /**< Function index code */ #define SP_ERROR_OUT_OF_MEMORY 28 /**< Out of memory */ #define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */ #define SP_ERROR_TIMEOUT 30 /**< Timeout */ +#define SP_ERROR_USER 31 /**< Custom message */ +#define SP_ERROR_FATAL 32 /**< Custom fatal message */ +#define SP_MAX_ERROR_CODES 33 //Hey you! Update the string table if you add to the end of me! */ /********************************************** diff --git a/sourcepawn/jit/AMBuilder b/sourcepawn/jit/AMBuilder index 721e6136..c3e04579 100644 --- a/sourcepawn/jit/AMBuilder +++ b/sourcepawn/jit/AMBuilder @@ -36,13 +36,13 @@ library.sources += [ 'code-allocator.cpp', 'code-stubs.cpp', 'compiled-function.cpp', - 'debug-trace.cpp', 'environment.cpp', 'file-utils.cpp', 'opcodes.cpp', 'plugin-context.cpp', 'plugin-runtime.cpp', 'scripted-invoker.cpp', + 'stack-frames.cpp', 'smx-v1-image.cpp', 'watchdog_timer.cpp', 'x86/code-stubs-x86.cpp', diff --git a/sourcepawn/jit/api.cpp b/sourcepawn/jit/api.cpp index dc54382f..9e347f23 100644 --- a/sourcepawn/jit/api.cpp +++ b/sourcepawn/jit/api.cpp @@ -177,14 +177,10 @@ SourcePawnEngine2::SourcePawnEngine2() { } -static size_t -UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) +size_t +sp::UTIL_FormatVA(char *buffer, size_t maxlength, const char *fmt, va_list ap) { - va_list ap; - - va_start(ap, fmt); size_t len = vsnprintf(buffer, maxlength, fmt, ap); - va_end(ap); if (len >= maxlength) { buffer[maxlength - 1] = '\0'; @@ -193,6 +189,18 @@ UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) return len; } +size_t +sp::UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + size_t len = UTIL_FormatVA(buffer, maxlength, fmt, ap); + va_end(ap); + + return len; +} + IPluginRuntime * SourcePawnEngine2::LoadPlugin(ICompilation *co, const char *file, int *err) { @@ -255,13 +263,13 @@ SourcePawnEngine2::LoadBinaryFromFile(const char *file, char *error, size_t maxl # endif ) { - pRuntime->SetName(&file[i + 1]); + pRuntime->SetNames(file, &file[i + 1]); break; } } if (!pRuntime->Name()) - pRuntime->SetName(file); + pRuntime->SetNames(file, file); return pRuntime; } @@ -340,7 +348,9 @@ SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t memory) return NULL; } - rt->SetName(name != NULL ? name : ""); + if (!name) + name = ""; + rt->SetNames(name, name); return rt; } @@ -386,3 +396,9 @@ SourcePawnEngine2::SetProfilingTool(IProfilingTool *tool) { Environment::get()->SetProfiler(tool); } + +ISourcePawnEnvironment * +SourcePawnEngine2::Environment() +{ + return Environment::get(); +} diff --git a/sourcepawn/jit/api.h b/sourcepawn/jit/api.h index d28248eb..5000f9a5 100644 --- a/sourcepawn/jit/api.h +++ b/sourcepawn/jit/api.h @@ -68,8 +68,12 @@ class SourcePawnEngine2 : public ISourcePawnEngine2 void DisableProfiling() KE_OVERRIDE; void SetProfilingTool(IProfilingTool *tool) KE_OVERRIDE; IPluginRuntime *LoadBinaryFromFile(const char *file, char *error, size_t maxlength) KE_OVERRIDE; + ISourcePawnEnvironment *Environment() KE_OVERRIDE; }; -} // namespace SourcePawn +extern size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); +extern size_t UTIL_FormatVA(char *buffer, size_t maxlength, const char *fmt, va_list ap); + +} // namespace sp #endif // _include_sourcepawn_vm_api_h_ diff --git a/sourcepawn/jit/code-stubs.cpp b/sourcepawn/jit/code-stubs.cpp index 252685c7..2906f586 100644 --- a/sourcepawn/jit/code-stubs.cpp +++ b/sourcepawn/jit/code-stubs.cpp @@ -18,8 +18,7 @@ using namespace sp; CodeStubs::CodeStubs(Environment *env) : env_(env), invoke_stub_(nullptr), - return_stub_(nullptr), - timeout_stub_(nullptr) + return_stub_(nullptr) { } diff --git a/sourcepawn/jit/code-stubs.h b/sourcepawn/jit/code-stubs.h index 65dece71..82cbcaae 100644 --- a/sourcepawn/jit/code-stubs.h +++ b/sourcepawn/jit/code-stubs.h @@ -40,9 +40,6 @@ class CodeStubs void *ReturnStub() const { return return_stub_; } - void *TimeoutStub() const { - return return_stub_; - } private: bool InitializeFeatureDetection(); @@ -52,7 +49,6 @@ class CodeStubs Environment *env_; void *invoke_stub_; void *return_stub_; // Owned by invoke_stub_. - void *timeout_stub_; // Owned by invoke_stub_. }; } diff --git a/sourcepawn/jit/compiled-function.cpp b/sourcepawn/jit/compiled-function.cpp index 3e1a5b0d..b720166a 100644 --- a/sourcepawn/jit/compiled-function.cpp +++ b/sourcepawn/jit/compiled-function.cpp @@ -16,10 +16,15 @@ using namespace sp; -CompiledFunction::CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray *edges) +CompiledFunction::CompiledFunction(void *entry_addr, size_t code_length, + cell_t pcode_offs, + FixedArray *edges, + FixedArray *cipmap) : entry_(entry_addr), + code_length_(code_length), code_offset_(pcode_offs), - edges_(edges) + edges_(edges), + cip_map_(cipmap) { } @@ -27,3 +32,40 @@ CompiledFunction::~CompiledFunction() { Environment::get()->FreeCode(entry_); } + +static int cip_map_entry_cmp(const void *a1, const void *aEntry) +{ + uint32_t pcoffs = (uint32_t)a1; + const CipMapEntry *entry = reinterpret_cast(aEntry); + if (pcoffs < entry->pcoffs) + return -1; + if (pcoffs == entry->pcoffs) + return 0; + return pcoffs > entry->pcoffs; +} + +ucell_t +CompiledFunction::FindCipByPc(void *pc) +{ + if (uintptr_t(pc) < uintptr_t(entry_)) + return kInvalidCip; + + uint32_t pcoffs = intptr_t(pc) - intptr_t(entry_); + if (pcoffs > code_length_) + return kInvalidCip; + + void *ptr = bsearch( + (void *)pcoffs, + cip_map_->buffer(), + cip_map_->length(), + sizeof(CipMapEntry), + cip_map_entry_cmp); + assert(ptr); + + if (!ptr) { + // Shouldn't happen, but fail gracefully. + return kInvalidCip; + } + + return code_offset_ + reinterpret_cast(ptr)->cipoffs; +} diff --git a/sourcepawn/jit/compiled-function.h b/sourcepawn/jit/compiled-function.h index 2a3181d4..a4ac782f 100644 --- a/sourcepawn/jit/compiled-function.h +++ b/sourcepawn/jit/compiled-function.h @@ -23,14 +23,31 @@ using namespace ke; struct LoopEdge { + // Offset to the patchable jump instruction, such that (base + offset - 4) + // yields a patchable location. uint32_t offset; + // The displacement to either the timeout routine or the original + // displacement, depending on the timeout state. int32_t disp32; }; +struct CipMapEntry { + // Offset from the first cip of the function. + uint32_t cipoffs; + // Offset from the first pc of the function. + uint32_t pcoffs; +}; + +static const ucell_t kInvalidCip = 0xffffffff; + class CompiledFunction { public: - CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray *edges); + CompiledFunction(void *entry_addr, + size_t code_length, + cell_t pcode_offs, + FixedArray *edges, + FixedArray *cip_map); ~CompiledFunction(); public: @@ -43,14 +60,18 @@ class CompiledFunction uint32_t NumLoopEdges() const { return edges_->length(); } - const LoopEdge &GetLoopEdge(size_t i) const { + LoopEdge &GetLoopEdge(size_t i) { return edges_->at(i); } + ucell_t FindCipByPc(void *pc); + private: void *entry_; + size_t code_length_; cell_t code_offset_; AutoPtr> edges_; + AutoPtr> cip_map_; }; } diff --git a/sourcepawn/jit/debug-trace.cpp b/sourcepawn/jit/debug-trace.cpp deleted file mode 100644 index cd082cb1..00000000 --- a/sourcepawn/jit/debug-trace.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// 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 "debug-trace.h" -#include "plugin-context.h" -#include "environment.h" -#include "plugin-runtime.h" - -using namespace ke; -using namespace sp; -using namespace SourcePawn; - -CContextTrace::CContextTrace(PluginRuntime *pRuntime, int err, const char *errstr, cell_t start_rp) - : m_pRuntime(pRuntime), - context_(pRuntime->GetBaseContext()), - m_Error(err), - m_pMsg(errstr), - m_StartRp(start_rp), - m_Level(0) -{ - m_pDebug = m_pRuntime->GetDebugInfo(); -} - -bool -CContextTrace::DebugInfoAvailable() -{ - return (m_pDebug != NULL); -} - -const char * -CContextTrace::GetCustomErrorString() -{ - return m_pMsg; -} - -int -CContextTrace::GetErrorCode() -{ - return m_Error; -} - -const char * -CContextTrace::GetErrorString() -{ - return Environment::get()->GetErrorString(m_Error); -} - -void -CContextTrace::ResetTrace() -{ - m_Level = 0; -} - -bool -CContextTrace::GetTraceInfo(CallStackInfo *trace) -{ - cell_t cip; - - if (m_Level == 0) { - cip = context_->cip(); - } else if (context_->rp() > 0) { - /* Entries go from ctx.rp - 1 to m_StartRp */ - cell_t offs, start, end; - - offs = m_Level - 1; - start = context_->rp() - 1; - end = m_StartRp; - - if (start - offs < end) - return false; - - cip = context_->getReturnStackCip(start - offs); - } else { - return false; - } - - if (trace == NULL) { - m_Level++; - return true; - } - - if (m_pDebug->LookupFile(cip, &(trace->filename)) != SP_ERROR_NONE) - trace->filename = NULL; - - if (m_pDebug->LookupFunction(cip, &(trace->function)) != SP_ERROR_NONE) - trace->function = NULL; - - if (m_pDebug->LookupLine(cip, &(trace->line)) != SP_ERROR_NONE) - trace->line = 0; - - m_Level++; - - return true; -} - -const char * -CContextTrace::GetLastNative(uint32_t *index) -{ - if (context_->GetLastNativeError() == SP_ERROR_NONE) - return NULL; - - int lastNative = context_->lastNative(); - if (lastNative < 0) - return NULL; - - const sp_native_t *native = m_pRuntime->GetNative(lastNative); - if (!native) - return NULL; - - if (index) - *index = lastNative; - - return native->name; -} diff --git a/sourcepawn/jit/debug-trace.h b/sourcepawn/jit/debug-trace.h deleted file mode 100644 index d902a7c6..00000000 --- a/sourcepawn/jit/debug-trace.h +++ /dev/null @@ -1,51 +0,0 @@ -// 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_sourcepawn_vm_debug_trace_h_ -#define _include_sourcepawn_vm_debug_trace_h_ - -#include - -namespace sp { - -using namespace SourcePawn; - -class PluginRuntime; -class PluginContext; - -class CContextTrace : public IContextTrace -{ - public: - CContextTrace(PluginRuntime *pRuntime, int err, const char *errstr, cell_t start_rp); - - public: - int GetErrorCode(); - const char *GetErrorString(); - bool DebugInfoAvailable(); - const char *GetCustomErrorString(); - bool GetTraceInfo(CallStackInfo *trace); - void ResetTrace(); - const char *GetLastNative(uint32_t *index); - - private: - PluginRuntime *m_pRuntime; - PluginContext *context_; - int m_Error; - const char *m_pMsg; - cell_t m_StartRp; - cell_t m_Level; - IPluginDebugInfo *m_pDebug; -}; - -} - -#endif // _include_sourcepawn_vm_debug_trace_h_ diff --git a/sourcepawn/jit/dll_exports.cpp b/sourcepawn/jit/dll_exports.cpp index 2fe8b94b..21362351 100644 --- a/sourcepawn/jit/dll_exports.cpp +++ b/sourcepawn/jit/dll_exports.cpp @@ -35,6 +35,7 @@ #include // Replace with am-cxx later. #include "dll_exports.h" #include "environment.h" +#include "stack-frames.h" using namespace ke; using namespace sp; @@ -55,72 +56,36 @@ public: } sFactory; #ifdef SPSHELL -template class AutoT -{ -public: - AutoT(T *t) - : t_(t) - { - } - ~AutoT() - { - delete t_; - } - - operator T *() const { - return t_; - } - bool operator !() const { - return !t_; - } - T * operator ->() const { - return t_; - } -private: - T *t_; -}; - Environment *sEnv; +static void +DumpStack(IFrameIterator &iter) +{ + int index = 0; + for (; !iter.Done(); iter.Next(), index++) { + const char *name = iter.FunctionName(); + if (!name) { + fprintf(stdout, " [%d] \n", index); + continue; + } + + if (iter.IsScriptedFrame()) { + const char *file = iter.FilePath(); + if (!file) + file = ""; + fprintf(stdout, " [%d] %s::%s, line %d\n", index, file, name, iter.LineNumber()); + } else { + fprintf(stdout, " [%d] %s()\n", index, name); + } + } +} + class ShellDebugListener : public IDebugListener { public: - void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) { - int n_err = error->GetErrorCode(); - - if (n_err != SP_ERROR_NATIVE) - { - fprintf(stderr, "plugin error: %s\n", error->GetErrorString()); - } - - if (const char *lastname = error->GetLastNative(NULL)) - { - if (const char *custerr = error->GetCustomErrorString()) - { - fprintf(stderr, "Native \"%s\" reported: %s", lastname, custerr); - } else { - fprintf(stderr, "Native \"%s\" encountered a generic error.", lastname); - } - } - - if (!error->DebugInfoAvailable()) - { - fprintf(stderr, "Debug info not available!\n"); - return; - } - - CallStackInfo stk_info; - int i = 0; - fprintf(stderr, "Displaying call stack trace:\n"); - while (error->GetTraceInfo(&stk_info)) - { - fprintf(stderr, - " [%d] Line %d, %s::%s()\n", - i++, - stk_info.line, - stk_info.filename, - stk_info.function); - } + void ReportError(const IErrorReport &report, IFrameIterator &iter) KE_OVERRIDE { + fprintf(stdout, "Exception thrown: %s\n", report.Message()); + DumpStack(iter); } void OnDebugSpew(const char *msg, ...) { @@ -181,18 +146,43 @@ static cell_t PrintFloat(IPluginContext *cx, const cell_t *params) return printf("%f\n", sp_ctof(params[1])); } +static cell_t DoExecute(IPluginContext *cx, const cell_t *params) +{ + int32_t ok = 0; + for (size_t i = 0; i < size_t(params[2]); i++) { + if (IPluginFunction *fn = cx->GetFunctionById(params[1])) { + if (fn->Execute(nullptr) != SP_ERROR_NONE) + continue; + ok++; + } + } + return ok; +} + +static cell_t DoInvoke(IPluginContext *cx, const cell_t *params) +{ + for (size_t i = 0; i < size_t(params[2]); i++) { + if (IPluginFunction *fn = cx->GetFunctionById(params[1])) { + if (!fn->Invoke()) + return 0; + } + } + return 1; +} + +static cell_t DumpStackTrace(IPluginContext *cx, const cell_t *params) +{ + FrameIterator iter; + DumpStack(iter); + return 0; +} + static int Execute(const char *file) { - ICompilation *co = sEnv->APIv2()->StartCompilation(); - if (!co) { - fprintf(stderr, "Could not create a compilation context\n"); - return 1; - } - - int err; - AutoT rt(sEnv->APIv2()->LoadPlugin(co, file, &err)); + char error[255]; + AutoPtr rt(sEnv->APIv2()->LoadBinaryFromFile(file, error, sizeof(error))); if (!rt) { - fprintf(stderr, "Could not load plugin: %s\n", sEnv->GetErrorString(err)); + fprintf(stderr, "Could not load plugin: %s\n", error); return 1; } @@ -201,6 +191,9 @@ static int Execute(const char *file) BindNative(rt, "printnums", PrintNums); BindNative(rt, "printfloat", PrintFloat); BindNative(rt, "donothing", DoNothing); + BindNative(rt, "execute", DoExecute); + BindNative(rt, "invoke", DoInvoke); + BindNative(rt, "dump_stack_trace", DumpStackTrace); IPluginFunction *fun = rt->GetFunctionByName("main"); if (!fun) @@ -208,10 +201,13 @@ static int Execute(const char *file) IPluginContext *cx = rt->GetDefaultContext(); - int result = fun->Execute2(cx, &err); - if (err != SP_ERROR_NONE) { - fprintf(stderr, "Error executing main(): %s\n", sEnv->GetErrorString(err)); - return 1; + int result; + { + ExceptionHandler eh(cx); + if (!fun->Invoke(&result)) { + fprintf(stderr, "Error executing main: %s\n", eh.Message()); + return 1; + } } return result; diff --git a/sourcepawn/jit/environment.cpp b/sourcepawn/jit/environment.cpp index 7c808817..3656f555 100644 --- a/sourcepawn/jit/environment.cpp +++ b/sourcepawn/jit/environment.cpp @@ -13,7 +13,6 @@ #include "environment.h" #include "x86/jit_x86.h" #include "watchdog_timer.h" -#include "debug-trace.h" #include "api.h" #include "code-stubs.h" #include "watchdog_timer.h" @@ -25,10 +24,12 @@ static Environment *sEnvironment = nullptr; Environment::Environment() : debugger_(nullptr), + exception_code_(SP_ERROR_NONE), profiler_(nullptr), jit_enabled_(true), profiling_enabled_(false), - code_pool_(nullptr) + code_pool_(nullptr), + top_(nullptr) { } @@ -150,7 +151,9 @@ static const char *sErrorMsgTable[] = "Plugin format is too new", "Out of memory", "Integer overflow", - "Script execution timed out" + "Script execution timed out", + "Custom error", + "Fatal error" }; const char * @@ -161,17 +164,6 @@ Environment::GetErrorString(int error) return sErrorMsgTable[error]; } -void -Environment::ReportError(PluginRuntime *runtime, int err, const char *errstr, cell_t rp_start) -{ - if (!debugger_) - return; - - CContextTrace trace(runtime, err, errstr, rp_start); - - debugger_->OnContextExecuteError(runtime->GetDefaultContext(), &trace); -} - void * Environment::AllocateCode(size_t size) { @@ -198,6 +190,15 @@ Environment::DeregisterRuntime(PluginRuntime *rt) runtimes_.remove(rt); } +static inline void +SwapLoopEdge(uint8_t *code, LoopEdge &e) +{ + int32_t *loc = reinterpret_cast(code + e.offset - 4); + int32_t new_disp32 = e.disp32; + e.disp32 = *loc; + *loc = new_disp32; +} + void Environment::PatchAllJumpsForTimeout() { @@ -208,11 +209,8 @@ Environment::PatchAllJumpsForTimeout() CompiledFunction *fun = rt->GetJitFunction(i); uint8_t *base = reinterpret_cast(fun->GetEntryAddress()); - for (size_t j = 0; j < fun->NumLoopEdges(); j++) { - const LoopEdge &e = fun->GetLoopEdge(j); - int32_t diff = intptr_t(code_stubs_->TimeoutStub()) - intptr_t(base + e.offset); - *reinterpret_cast(base + e.offset - 4) = diff; - } + for (size_t j = 0; j < fun->NumLoopEdges(); j++) + SwapLoopEdge(base, fun->GetLoopEdge(j)); } } } @@ -227,10 +225,8 @@ Environment::UnpatchAllJumpsFromTimeout() CompiledFunction *fun = rt->GetJitFunction(i); uint8_t *base = reinterpret_cast(fun->GetEntryAddress()); - for (size_t j = 0; j < fun->NumLoopEdges(); j++) { - const LoopEdge &e = fun->GetLoopEdge(j); - *reinterpret_cast(base + e.offset - 4) = e.disp32; - } + for (size_t j = 0; j < fun->NumLoopEdges(); j++) + SwapLoopEdge(base, fun->GetLoopEdge(j)); } } } @@ -238,16 +234,168 @@ Environment::UnpatchAllJumpsFromTimeout() int Environment::Invoke(PluginRuntime *runtime, CompiledFunction *fn, cell_t *result) { + // Must be in an invoke frame. + assert(top_ && top_->cx() == runtime->GetBaseContext()); + PluginContext *cx = runtime->GetBaseContext(); - // Note that cip, hp, sp are saved and restored by Execute2(). - *cx->addressOfCip() = fn->GetCodeOffset(); - InvokeStubFn invoke = code_stubs_->InvokeStub(); - - EnterInvoke(); - int err = invoke(cx, fn->GetEntryAddress(), result); - LeaveInvoke(); - - return err; + return invoke(cx, fn->GetEntryAddress(), result); +} + +void +Environment::ReportError(int code) +{ + const char *message = GetErrorString(code); + if (!message) { + char buffer[255]; + UTIL_Format(buffer, sizeof(buffer), "Unknown error code %d", code); + ReportError(code, buffer); + } else { + ReportError(code, message); + } +} + +class ErrorReport : public SourcePawn::IErrorReport +{ + public: + ErrorReport(int code, const char *message, PluginContext *cx) + : code_(code), + message_(message), + context_(cx) + {} + + const char *Message() const KE_OVERRIDE { + return message_; + } + bool IsFatal() const KE_OVERRIDE { + switch (code_) { + case SP_ERROR_HEAPLOW: + case SP_ERROR_INVALID_ADDRESS: + case SP_ERROR_STACKLOW: + case SP_ERROR_INVALID_INSTRUCTION: + case SP_ERROR_MEMACCESS: + case SP_ERROR_STACKMIN: + case SP_ERROR_HEAPMIN: + case SP_ERROR_INSTRUCTION_PARAM: + case SP_ERROR_STACKLEAK: + case SP_ERROR_HEAPLEAK: + case SP_ERROR_TRACKER_BOUNDS: + case SP_ERROR_PARAMS_MAX: + case SP_ERROR_ABORTED: + case SP_ERROR_OUT_OF_MEMORY: + case SP_ERROR_FATAL: + return true; + default: + return false; + } + } + IPluginContext *Context() const KE_OVERRIDE { + return context_; + } + + private: + int code_; + const char *message_; + PluginContext *context_; +}; + +void +Environment::ReportErrorVA(const char *fmt, va_list ap) +{ + ReportErrorVA(SP_ERROR_USER, fmt, ap); +} + +void +Environment::ReportErrorVA(int code, const char *fmt, va_list ap) +{ + // :TODO: right-size the string rather than rely on this buffer. + char buffer[1024]; + UTIL_FormatVA(buffer, sizeof(buffer), fmt, ap); + ReportError(code, buffer); +} + +void +Environment::ReportErrorFmt(int code, const char *message, ...) +{ + va_list ap; + va_start(ap, message); + ReportErrorVA(code, message, ap); + va_end(ap); +} + +void +Environment::ReportError(int code, const char *message) +{ + FrameIterator iter; + ErrorReport report(code, message, top_ ? top_->cx() : nullptr); + + // If this fires, someone forgot to propagate an error. + assert(!hasPendingException()); + + // Save the exception state. + if (eh_top_) { + exception_code_ = code; + UTIL_Format(exception_message_, sizeof(exception_message_), "%s", message); + } + + // For now, we always report exceptions even if they might be handled. + if (debugger_) + debugger_->ReportError(report, iter); +} + +void +Environment::EnterExceptionHandlingScope(ExceptionHandler *handler) +{ + handler->next_ = eh_top_; + eh_top_ = handler; +} + +void +Environment::LeaveExceptionHandlingScope(ExceptionHandler *handler) +{ + assert(handler == eh_top_); + eh_top_ = eh_top_->next_; + + // To preserve compatibility with older API, we clear the exception state + // when there is no EH handler. + if (!eh_top_ || handler->catch_) + exception_code_ = SP_ERROR_NONE; +} + +bool +Environment::HasPendingException(const ExceptionHandler *handler) +{ + // Note here and elsewhere - this is not a sanity assert. In the future, the + // API may need to query the handler. + assert(handler == eh_top_); + return hasPendingException(); +} + +const char * +Environment::GetPendingExceptionMessage(const ExceptionHandler *handler) +{ + // Note here and elsewhere - this is not a sanity assert. In the future, the + // API may need to query the handler. + assert(handler == eh_top_); + assert(HasPendingException(handler)); + return exception_message_; +} + +bool +Environment::hasPendingException() const +{ + return exception_code_ != SP_ERROR_NONE; +} + +void +Environment::clearPendingException() +{ + exception_code_ = SP_ERROR_NONE; +} + +int +Environment::getPendingExceptionCode() const +{ + return exception_code_; } diff --git a/sourcepawn/jit/environment.h b/sourcepawn/jit/environment.h index 85bf7db8..7e6f8cfe 100644 --- a/sourcepawn/jit/environment.h +++ b/sourcepawn/jit/environment.h @@ -19,6 +19,7 @@ #include #include "code-allocator.h" #include "plugin-runtime.h" +#include "stack-frames.h" namespace sp { @@ -54,9 +55,18 @@ class Environment : public ISourcePawnEnvironment bool InstallWatchdogTimer(int timeout_ms); + void EnterExceptionHandlingScope(ExceptionHandler *handler) KE_OVERRIDE; + void LeaveExceptionHandlingScope(ExceptionHandler *handler) KE_OVERRIDE; + bool HasPendingException(const ExceptionHandler *handler) KE_OVERRIDE; + const char *GetPendingExceptionMessage(const ExceptionHandler *handler) KE_OVERRIDE; + // Runtime functions. const char *GetErrorString(int err); - void ReportError(PluginRuntime *runtime, int err, const char *errstr, cell_t rp_start); + void ReportError(int code); + void ReportError(int code, const char *message); + void ReportErrorFmt(int code, const char *message, ...); + void ReportErrorVA(const char *fmt, va_list ap); + void ReportErrorVA(int code, const char *fmt, va_list ap); // Allocate and free executable memory. void *AllocateCode(size_t size); @@ -104,19 +114,40 @@ class Environment : public ISourcePawnEnvironment return watchdog_timer_; } + bool hasPendingException() const; + void clearPendingException(); + int getPendingExceptionCode() const; + // These are indicators used for the watchdog timer. uintptr_t FrameId() const { return frame_id_; } bool RunningCode() const { - return invoke_depth_ != 0; + return !!top_; } - void EnterInvoke() { - if (invoke_depth_++ == 0) + void enterInvoke(InvokeFrame *frame) { + if (!top_) frame_id_++; + top_ = frame; } - void LeaveInvoke() { - invoke_depth_--; + void leaveInvoke() { + exit_frame_ = top_->prev_exit_frame(); + top_ = top_->prev(); + } + + InvokeFrame *top() const { + return top_; + } + const ExitFrame &exit_frame() const { + return exit_frame_; + } + + public: + static inline size_t offsetOfTopFrame() { + return offsetof(Environment, top_); + } + static inline size_t offsetOfExceptionCode() { + return offsetof(Environment, exception_code_); } private: @@ -129,6 +160,10 @@ class Environment : public ISourcePawnEnvironment ke::Mutex mutex_; IDebugListener *debugger_; + ExceptionHandler *eh_top_; + int exception_code_; + char exception_message_[1024]; + IProfilingTool *profiler_; bool jit_enabled_; bool profiling_enabled_; @@ -137,9 +172,11 @@ class Environment : public ISourcePawnEnvironment ke::InlineList runtimes_; uintptr_t frame_id_; - uintptr_t invoke_depth_; ke::AutoPtr code_stubs_; + + InvokeFrame *top_; + ExitFrame exit_frame_; }; class EnterProfileScope diff --git a/sourcepawn/jit/plugin-context.cpp b/sourcepawn/jit/plugin-context.cpp index 1fe6aa5a..7470c787 100644 --- a/sourcepawn/jit/plugin-context.cpp +++ b/sourcepawn/jit/plugin-context.cpp @@ -30,14 +30,13 @@ using namespace SourcePawn; static const size_t kMinHeapSize = 16384; PluginContext::PluginContext(PluginRuntime *pRuntime) - : m_pRuntime(pRuntime), + : env_(Environment::get()), + m_pRuntime(pRuntime), memory_(nullptr), data_size_(m_pRuntime->data().length), mem_size_(m_pRuntime->image()->HeapSize()), m_pNullVec(nullptr), - m_pNullString(nullptr), - m_CustomMsg(false), - m_InExec(false) + m_pNullString(nullptr) { // Compute and align a minimum memory amount. if (mem_size_ < data_size_) @@ -52,9 +51,6 @@ PluginContext::PluginContext(PluginRuntime *pRuntime) hp_ = data_size_; sp_ = mem_size_ - sizeof(cell_t); frm_ = sp_; - rp_ = 0; - last_native_ = -1; - native_error_ = SP_ERROR_NONE; tracker_.pBase = (ucell_t *)malloc(1024); tracker_.pCur = tracker_.pBase; @@ -133,56 +129,23 @@ PluginContext::Execute(uint32_t code_addr, cell_t *result) return SP_ERROR_ABORTED; } -void -PluginContext::SetErrorMessage(const char *msg, va_list ap) -{ - m_CustomMsg = true; - - vsnprintf(m_MsgCache, sizeof(m_MsgCache), msg, ap); -} - -void -PluginContext::_SetErrorMessage(const char *msg, ...) -{ - va_list ap; - va_start(ap, msg); - SetErrorMessage(msg, ap); - va_end(ap); -} - cell_t PluginContext::ThrowNativeErrorEx(int error, const char *msg, ...) { - if (!m_InExec) - return 0; - - native_error_ = error; - - if (msg) { - va_list ap; - va_start(ap, msg); - SetErrorMessage(msg, ap); - va_end(ap); - } - + va_list ap; + va_start(ap, msg); + env_->ReportErrorVA(error, msg, ap); + va_end(ap); return 0; } cell_t PluginContext::ThrowNativeError(const char *msg, ...) { - if (!m_InExec) - return 0; - - native_error_ = SP_ERROR_NATIVE; - - if (msg) { - va_list ap; - va_start(ap, msg); - SetErrorMessage(msg, ap); - va_end(ap); - } - + va_list ap; + va_start(ap, msg); + env_->ReportErrorVA(SP_ERROR_NATIVE, msg, ap); + va_end(ap); return 0; } @@ -540,133 +503,124 @@ PluginContext::GetNullRef(SP_NULL_TYPE type) bool PluginContext::IsInExec() { - return m_InExec; + for (InvokeFrame *ivk = env_->top(); ivk; ivk = ivk->prev()) { + if (ivk->cx() == this) + return true; + } + return false; } int PluginContext::Execute2(IPluginFunction *function, const cell_t *params, unsigned int num_params, cell_t *result) { - int ir; - int serial; - cell_t *sp; - CompiledFunction *fn; - cell_t _ignore_result; + ReportErrorNumber(SP_ERROR_ABORTED); + return SP_ERROR_ABORTED; +} +bool +PluginContext::Invoke(funcid_t fnid, const cell_t *params, unsigned int num_params, cell_t *result) +{ EnterProfileScope profileScope("SourcePawn", "EnterJIT"); - if (!Environment::get()->watchdog()->HandleInterrupt()) - return SP_ERROR_TIMEOUT; + if (!env_->watchdog()->HandleInterrupt()) { + ReportErrorNumber(SP_ERROR_TIMEOUT); + return false; + } - funcid_t fnid = function->GetFunctionID(); - if (!(fnid & 1)) - return SP_ERROR_INVALID_ADDRESS; + assert((fnid & 1) != 0); unsigned public_id = fnid >> 1; ScriptedInvoker *cfun = m_pRuntime->GetPublicFunction(public_id); - if (!cfun) - return SP_ERROR_NOT_FOUND; + if (!cfun) { + ReportErrorNumber(SP_ERROR_NOT_FOUND); + return false; + } - if (m_pRuntime->IsPaused()) - return SP_ERROR_NOT_RUNNABLE; + if (m_pRuntime->IsPaused()) { + ReportErrorNumber(SP_ERROR_NOT_RUNNABLE); + return false; + } - if ((cell_t)(hp_ + 16*sizeof(cell_t)) > (cell_t)(sp_ - (sizeof(cell_t) * (num_params + 1)))) - return SP_ERROR_STACKLOW; + if ((cell_t)(hp_ + 16*sizeof(cell_t)) > (cell_t)(sp_ - (sizeof(cell_t) * (num_params + 1)))) { + ReportErrorNumber(SP_ERROR_STACKLOW); + return false; + } + // Yuck. We have to do this for compatibility, otherwise something like + // ForwardSys or any sort of multi-callback-fire code would die. Later, + // we'll expose an Invoke() or something that doesn't do this. + env_->clearPendingException(); + + cell_t ignore_result; if (result == NULL) - result = &_ignore_result; + result = &ignore_result; /* We got this far. It's time to start profiling. */ EnterProfileScope scriptScope("SourcePawn", cfun->FullName()); /* See if we have to compile the callee. */ - if (Environment::get()->IsJitEnabled()) { + CompiledFunction *fn = nullptr; + if (env_->IsJitEnabled()) { /* We might not have to - check pcode offset. */ if ((fn = cfun->cachedCompiledFunction()) == nullptr) { fn = m_pRuntime->GetJittedFunctionByOffset(cfun->Public()->code_offs); if (!fn) { - if ((fn = CompileFunction(m_pRuntime, cfun->Public()->code_offs, &ir)) == NULL) - return ir; + int err = SP_ERROR_NONE; + if ((fn = CompileFunction(m_pRuntime, cfun->Public()->code_offs, &err)) == NULL) { + ReportErrorNumber(err); + return false; + } } cfun->setCachedCompiledFunction(fn); } + } else { + ReportError("JIT is not enabled!"); + return false; } /* Save our previous state. */ - - bool save_exec; - uint32_t save_n_idx; - cell_t save_sp, save_hp, save_rp, save_cip; - - save_sp = sp_; - save_hp = hp_; - save_exec = m_InExec; - save_n_idx = last_native_; - save_rp = rp_; - save_cip = cip_; + cell_t save_sp = sp_; + cell_t save_hp = hp_; /* Push parameters */ - sp_ -= sizeof(cell_t) * (num_params + 1); - sp = (cell_t *)(memory_ + sp_); + cell_t *sp = (cell_t *)(memory_ + sp_); sp[0] = num_params; for (unsigned int i = 0; i < num_params; i++) sp[i + 1] = params[i]; - /* Clear internal state */ - native_error_ = SP_ERROR_NONE; - last_native_ = -1; - m_MsgCache[0] = '\0'; - m_CustomMsg = false; - m_InExec = true; - // Enter the execution engine. - Environment *env = Environment::get(); - ir = env->Invoke(m_pRuntime, fn, result); - - /* Restore some states, stop the frame tracer */ - - m_InExec = save_exec; + int ir; + { + InvokeFrame ivkframe(this, fn->GetCodeOffset()); + Environment *env = env_; + ir = env->Invoke(m_pRuntime, fn, result); + } if (ir == SP_ERROR_NONE) { - native_error_ = SP_ERROR_NONE; + // Verify that our state is still sane. if (sp_ != save_sp) { - ir = SP_ERROR_STACKLEAK; - _SetErrorMessage("Stack leak detected: sp:%d should be %d!", + env_->ReportErrorFmt( + SP_ERROR_STACKLEAK, + "Stack leak detected: sp:%d should be %d!", sp_, save_sp); + return false; } if (hp_ != save_hp) { - ir = SP_ERROR_HEAPLEAK; - _SetErrorMessage("Heap leak detected: hp:%d should be %d!", + env_->ReportErrorFmt( + SP_ERROR_HEAPLEAK, + "Heap leak detected: hp:%d should be %d!", hp_, save_hp); - } - if (rp_ != save_rp) { - ir = SP_ERROR_STACKLEAK; - _SetErrorMessage("Return stack leak detected: rp:%d should be %d!", - rp_, - save_rp); + return false; } } - if (ir == SP_ERROR_TIMEOUT) - Environment::get()->watchdog()->NotifyTimeoutReceived(); - - if (ir != SP_ERROR_NONE) - Environment::get()->ReportError(m_pRuntime, ir, m_MsgCache, save_rp); - sp_ = save_sp; hp_ = save_hp; - rp_ = save_rp; - - cip_ = save_cip; - last_native_ = save_n_idx; - native_error_ = SP_ERROR_NONE; - m_MsgCache[0] = '\0'; - m_CustomMsg = false; - - return ir; + return ir == SP_ERROR_NONE; } IPluginRuntime * @@ -678,7 +632,10 @@ PluginContext::GetRuntime() int PluginContext::GetLastNativeError() { - return native_error_; + Environment *env = env_; + if (!env->hasPendingException()) + return SP_ERROR_NONE; + return env->getPendingExceptionCode(); } cell_t * @@ -710,7 +667,8 @@ PluginContext::GetKey(int k, void **value) void PluginContext::ClearLastNativeError() { - native_error_ = SP_ERROR_NONE; + if (env_->hasPendingException()) + env_->clearPendingException(); } int @@ -757,28 +715,24 @@ PluginContext::invokeNative(ucell_t native_idx, cell_t *params) cell_t save_sp = sp_; cell_t save_hp = hp_; - // Note: Invoke() saves the last native, so we don't need to here. - last_native_ = native_idx; - const sp_native_t *native = m_pRuntime->GetNative(native_idx); if (native->status == SP_NATIVE_UNBOUND) { - native_error_ = SP_ERROR_INVALID_NATIVE; + ReportErrorNumber(SP_ERROR_INVALID_NATIVE); return 0; } cell_t result = native->pfn(this, params); - if (native_error_ != SP_ERROR_NONE) - return result; - if (save_sp != sp_) { - native_error_ = SP_ERROR_STACKLEAK; - return result; + if (!env_->hasPendingException()) + ReportErrorNumber(SP_ERROR_STACKLEAK); + return 0; } if (save_hp != hp_) { - native_error_ = SP_ERROR_HEAPLEAK; - return result; + if (!env_->hasPendingException()) + ReportErrorNumber(SP_ERROR_HEAPLEAK); + return 0; } return result; @@ -792,15 +746,14 @@ PluginContext::invokeBoundNative(SPVM_NATIVE_FUNC pfn, cell_t *params) cell_t result = pfn(this, params); - if (native_error_ != SP_ERROR_NONE) - return result; - if (save_sp != sp_) { - native_error_ = SP_ERROR_STACKLEAK; + if (!env_->hasPendingException()) + ReportErrorNumber(SP_ERROR_STACKLEAK); return result; } if (save_hp != hp_) { - native_error_ = SP_ERROR_HEAPLEAK; + if (!env_->hasPendingException()) + ReportErrorNumber(SP_ERROR_HEAPLEAK); return result; } @@ -957,3 +910,44 @@ PluginContext::generateArray(cell_t dims, cell_t *stk, bool autozero) return SP_ERROR_NONE; } +ISourcePawnEngine2 * +PluginContext::APIv2() +{ + return env_->APIv2(); +} + +void +PluginContext::ReportError(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + env_->ReportErrorVA(fmt, ap); + va_end(ap); +} + +void +PluginContext::ReportErrorVA(const char *fmt, va_list ap) +{ + env_->ReportErrorVA(fmt, ap); +} + +void +PluginContext::ReportFatalError(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + env_->ReportErrorVA(SP_ERROR_FATAL, fmt, ap); + va_end(ap); +} + +void +PluginContext::ReportFatalErrorVA(const char *fmt, va_list ap) +{ + env_->ReportErrorVA(SP_ERROR_FATAL, fmt, ap); +} + +void +PluginContext::ReportErrorNumber(int error) +{ + env_->ReportError(error); +} diff --git a/sourcepawn/jit/plugin-context.h b/sourcepawn/jit/plugin-context.h index acce68f1..99517f81 100644 --- a/sourcepawn/jit/plugin-context.h +++ b/sourcepawn/jit/plugin-context.h @@ -33,6 +33,9 @@ struct HeapTracker static const size_t SP_MAX_RETURN_STACK = 1024; +class Environment; +class PluginContext; + class PluginContext : public IPluginContext { public: @@ -88,6 +91,14 @@ class PluginContext : public IPluginContext bool GetKey(int k, void **value); void Refresh(); void ClearLastNativeError(); + ISourcePawnEngine2 *APIv2() KE_OVERRIDE; + void ReportError(const char *fmt, ...) KE_OVERRIDE; + void ReportErrorVA(const char *fmt, va_list ap) KE_OVERRIDE; + void ReportFatalError(const char *fmt, ...) KE_OVERRIDE; + void ReportFatalErrorVA(const char *fmt, va_list ap) KE_OVERRIDE; + void ReportErrorNumber(int error) KE_OVERRIDE; + + bool Invoke(funcid_t fnid, const cell_t *params, unsigned int num_params, cell_t *result); size_t HeapSize() const { return mem_size_; @@ -98,25 +109,16 @@ class PluginContext : public IPluginContext size_t DataSize() const { return data_size_; } + PluginRuntime *runtime() const { + return m_pRuntime; + } public: bool IsInExec(); - static inline size_t offsetOfRp() { - return offsetof(PluginContext, rp_); - } - static inline size_t offsetOfRstkCips() { - return offsetof(PluginContext, rstk_cips_); - } static inline size_t offsetOfTracker() { return offsetof(PluginContext, tracker_); } - static inline size_t offsetOfLastNative() { - return offsetof(PluginContext, last_native_); - } - static inline size_t offsetOfNativeError() { - return offsetof(PluginContext, native_error_); - } static inline size_t offsetOfSp() { return offsetof(PluginContext, sp_); } @@ -127,9 +129,6 @@ class PluginContext : public IPluginContext return offsetof(PluginContext, memory_); } - int32_t *addressOfCip() { - return &cip_; - } int32_t *addressOfSp() { return &sp_; } @@ -140,9 +139,6 @@ class PluginContext : public IPluginContext return &hp_; } - int32_t cip() const { - return cip_; - } cell_t frm() const { return frm_; } @@ -150,25 +146,6 @@ class PluginContext : public IPluginContext return hp_; } - // Return stack logic. - bool pushReturnCip(cell_t cip) { - if (rp_ >= SP_MAX_RETURN_STACK) - return false; - rstk_cips_[rp_++] = cip; - return true; - } - void popReturnCip() { - assert(rp_ > 0); - rp_--; - } - cell_t rp() const { - return rp_; - } - cell_t getReturnStackCip(int index) { - assert(index >= 0 && index < SP_MAX_RETURN_STACK); - return rstk_cips_[index]; - } - int popTrackerAndSetHeap(); int pushTracker(uint32_t amount); @@ -176,9 +153,6 @@ class PluginContext : public IPluginContext int generateFullArray(uint32_t argc, cell_t *argv, int autozero); cell_t invokeNative(ucell_t native_idx, cell_t *params); cell_t invokeBoundNative(SPVM_NATIVE_FUNC pfn, cell_t *params); - int lastNative() const { - return last_native_; - } inline bool checkAddress(cell_t *stk, cell_t addr) { if (uint32_t(addr) >= mem_size_) @@ -194,10 +168,7 @@ class PluginContext : public IPluginContext } private: - void SetErrorMessage(const char *msg, va_list ap); - void _SetErrorMessage(const char *msg, ...); - - private: + Environment *env_; PluginRuntime *m_pRuntime; uint8_t *memory_; uint32_t data_size_; @@ -205,26 +176,12 @@ class PluginContext : public IPluginContext cell_t *m_pNullVec; cell_t *m_pNullString; - char m_MsgCache[1024]; - bool m_CustomMsg; - bool m_InExec; void *m_keys[4]; bool m_keys_set[4]; // Tracker for local HEA growth. HeapTracker tracker_; - // Return stack. - cell_t rp_; - cell_t rstk_cips_[SP_MAX_RETURN_STACK]; - - // Track the currently executing native index, and any error it throws. - int32_t last_native_; - int native_error_; - - // Most recent CIP. - int32_t cip_; - // Stack, heap, and frame pointer. cell_t sp_; cell_t hp_; diff --git a/sourcepawn/jit/plugin-runtime.cpp b/sourcepawn/jit/plugin-runtime.cpp index 15bd856e..7a700e77 100644 --- a/sourcepawn/jit/plugin-runtime.cpp +++ b/sourcepawn/jit/plugin-runtime.cpp @@ -161,11 +161,10 @@ PluginRuntime::GetNativeReplacement(size_t index) } void -PluginRuntime::SetName(const char *name) +PluginRuntime::SetNames(const char *fullname, const char *name) { - size_t len = strlen(name); - name_ = new char[len + 1]; - strcpy(name_, name); + name_ = name; + full_name_ = name; } static cell_t @@ -180,19 +179,21 @@ PluginRuntime::AddJittedFunction(CompiledFunction *fn) m_JitFunctions.append(fn); ucell_t pcode_offset = fn->GetCodeOffset(); - FunctionMap::Insert p = function_map_.findForAdd(pcode_offset); - assert(!p.found()); + { + FunctionMap::Insert p = function_map_.findForAdd(pcode_offset); + assert(!p.found()); - function_map_.add(p, pcode_offset, fn); + function_map_.add(p, pcode_offset, fn); + } } CompiledFunction * PluginRuntime::GetJittedFunctionByOffset(cell_t pcode_offset) { FunctionMap::Result r = function_map_.find(pcode_offset); - if (r.found()) - return r->value; - return nullptr; + if (!r.found()) + return nullptr; + return r->value; } int diff --git a/sourcepawn/jit/plugin-runtime.h b/sourcepawn/jit/plugin-runtime.h index 04ca551e..0412b675 100644 --- a/sourcepawn/jit/plugin-runtime.h +++ b/sourcepawn/jit/plugin-runtime.h @@ -15,6 +15,7 @@ #include #include +#include #include #include #include "compiled-function.h" @@ -71,7 +72,7 @@ class PluginRuntime virtual unsigned char *GetDataHash(); CompiledFunction *GetJittedFunctionByOffset(cell_t pcode_offset); void AddJittedFunction(CompiledFunction *fn); - void SetName(const char *name); + void SetNames(const char *fullname, const char *name); unsigned GetNativeReplacement(size_t index); ScriptedInvoker *GetPublicFunction(size_t index); int UpdateNativeBinding(uint32_t index, SPVM_NATIVE_FUNC pfn, uint32_t flags, void *data) KE_OVERRIDE; @@ -79,6 +80,9 @@ class PluginRuntime int LookupLine(ucell_t addr, uint32_t *line) KE_OVERRIDE; int LookupFunction(ucell_t addr, const char **name) KE_OVERRIDE; int LookupFile(ucell_t addr, const char **filename) KE_OVERRIDE; + const char *GetFilename() KE_OVERRIDE { + return full_name_.chars(); + } PluginContext *GetBaseContext(); @@ -89,11 +93,7 @@ class PluginRuntime return m_JitFunctions[i]; } const char *Name() const { - return name_; - } - - static inline size_t offsetToPlugin() { - return 0x0fff0000; + return name_.chars(); } public: @@ -117,7 +117,8 @@ class PluginRuntime ke::AutoPtr image_; ke::AutoArray aligned_code_; ke::AutoArray float_table_; - ke::AutoArray name_; + ke::AString name_; + ke::AString full_name_; Code code_; Data data_; ke::AutoArray natives_; diff --git a/sourcepawn/jit/scripted-invoker.cpp b/sourcepawn/jit/scripted-invoker.cpp index 251dd29a..c6acd2d7 100644 --- a/sourcepawn/jit/scripted-invoker.cpp +++ b/sourcepawn/jit/scripted-invoker.cpp @@ -15,6 +15,8 @@ #include #include "scripted-invoker.h" #include "plugin-runtime.h" +#include "environment.h" +#include "plugin-context.h" /******************** * FUNCTION CALLING * @@ -23,43 +25,14 @@ using namespace sp; using namespace SourcePawn; -ScriptedInvoker::~ScriptedInvoker() -{ - delete [] full_name_; -} - -bool -ScriptedInvoker::IsRunnable() -{ - return !m_pRuntime->IsPaused(); -} - -int -ScriptedInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) -{ - return CallFunction2(m_pRuntime->GetDefaultContext(), params, num_params, result); -} - -int -ScriptedInvoker::CallFunction2(IPluginContext *pContext, const cell_t *params, unsigned int num_params, cell_t *result) -{ - return pContext->Execute2(this, params, num_params, result); -} - -IPluginContext * -ScriptedInvoker::GetParentContext() -{ - return m_pRuntime->GetDefaultContext(); -} - ScriptedInvoker::ScriptedInvoker(PluginRuntime *runtime, funcid_t id, uint32_t pub_id) - : m_curparam(0), + : env_(Environment::get()), + context_(runtime->GetBaseContext()), + m_curparam(0), m_errorstate(SP_ERROR_NONE), m_FnId(id), cc_function_(nullptr) { - m_pRuntime = runtime; - runtime->GetPublicByIndex(pub_id, &public_); size_t rt_len = strlen(runtime->Name()); @@ -71,6 +44,36 @@ ScriptedInvoker::ScriptedInvoker(PluginRuntime *runtime, funcid_t id, uint32_t p strcpy(&full_name_[rt_len + 2], public_->name); } +ScriptedInvoker::~ScriptedInvoker() +{ +} + +bool +ScriptedInvoker::IsRunnable() +{ + return !context_->runtime()->IsPaused(); +} + +int +ScriptedInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) +{ + Environment::get()->ReportError(SP_ERROR_ABORTED); + return SP_ERROR_ABORTED; +} + +int +ScriptedInvoker::CallFunction2(IPluginContext *pContext, const cell_t *params, unsigned int num_params, cell_t *result) +{ + Environment::get()->ReportError(SP_ERROR_ABORTED); + return SP_ERROR_ABORTED; +} + +IPluginContext * +ScriptedInvoker::GetParentContext() +{ + return context_; +} + int ScriptedInvoker::PushCell(cell_t cell) { if (m_curparam >= SP_MAX_EXEC_PARAMS) @@ -169,21 +172,41 @@ ScriptedInvoker::Cancel() int ScriptedInvoker::Execute(cell_t *result) { - return Execute2(m_pRuntime->GetDefaultContext(), result); + Environment *env = Environment::get(); + env->clearPendingException(); + + // 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. + ExceptionHandler eh(context_); + if (!Invoke(result)) { + assert(env->hasPendingException()); + return env->getPendingExceptionCode(); + } + + return SP_ERROR_NONE; } -int -ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) +bool +ScriptedInvoker::Invoke(cell_t *result) { - int err = SP_ERROR_NONE; - - if (!IsRunnable()) - m_errorstate = SP_ERROR_NOT_RUNNABLE; - - if (m_errorstate != SP_ERROR_NONE) { - err = m_errorstate; + if (!IsRunnable()) { Cancel(); - return err; + env_->ReportError(SP_ERROR_NOT_RUNNABLE); + return false; + } + if (int err = m_errorstate) { + Cancel(); + env_->ReportError(err); + return false; } //This is for re-entrancy! @@ -191,7 +214,6 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) ParamInfo temp_info[SP_MAX_EXEC_PARAMS]; unsigned int numparams = m_curparam; unsigned int i; - bool docopies = true; if (numparams) { @@ -201,16 +223,19 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) m_curparam = 0; /* 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))) - != SP_ERROR_NONE) - { + int err = context_->HeapAlloc( + temp_info[i].size, + &(temp_info[i].local_addr), + &(temp_info[i].phys_addr)); + if (err != SP_ERROR_NONE) { + env_->ReportError(err); + ok = false; break; } if (temp_info[i].orig_addr) @@ -222,26 +247,26 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) size_t cells = (temp_info[i].size + sizeof(cell_t) - 1) / sizeof(cell_t); /* Allocate the buffer */ - if ((err=ctx->HeapAlloc(cells, - &(temp_info[i].local_addr), - &(temp_info[i].phys_addr))) - != SP_ERROR_NONE) - { + int err = context_->HeapAlloc( + cells, + &(temp_info[i].local_addr), + &(temp_info[i].phys_addr)); + if (err != SP_ERROR_NONE) { + env_->ReportError(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) { - if ((err=ctx->StringToLocalUTF8(temp_info[i].local_addr, - temp_info[i].size, - (const char *)temp_info[i].orig_addr, - NULL)) - != SP_ERROR_NONE) - { - break; - } + 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) @@ -251,13 +276,10 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) /* Copy ASCII characters */ else { - if ((err=ctx->StringToLocal(temp_info[i].local_addr, - temp_info[i].size, - (const char *)temp_info[i].orig_addr)) - != SP_ERROR_NONE) - { - break; - } + context_->StringToLocal( + temp_info[i].local_addr, + temp_info[i].size, + (const char *)temp_info[i].orig_addr); } } } /* End array/string calculation */ @@ -270,14 +292,11 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) } /* Make the call if we can */ - if (err == SP_ERROR_NONE) { - if ((err = CallFunction2(ctx, temp_params, numparams, result)) != SP_ERROR_NONE) - docopies = false; - } else { - docopies = false; - } + if (ok) + ok = context_->Invoke(m_FnId, temp_params, numparams, result); /* i should be equal to the last valid parameter + 1 */ + bool docopies = ok; while (i--) { if (!temp_info[i].marked) continue; @@ -299,17 +318,24 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) } } - if ((err=ctx->HeapPop(temp_info[i].local_addr)) != SP_ERROR_NONE) - return err; + if (int err = context_->HeapPop(temp_info[i].local_addr)) + env_->ReportError(err); } - return err; + return !env_->hasPendingException(); +} + +int +ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) +{ + Environment::get()->ReportError(SP_ERROR_ABORTED); + return SP_ERROR_ABORTED; } IPluginRuntime * ScriptedInvoker::GetParentRuntime() { - return m_pRuntime; + return context_->runtime(); } funcid_t diff --git a/sourcepawn/jit/scripted-invoker.h b/sourcepawn/jit/scripted-invoker.h index 0e63c84b..b9911da3 100644 --- a/sourcepawn/jit/scripted-invoker.h +++ b/sourcepawn/jit/scripted-invoker.h @@ -14,12 +14,14 @@ #define _INCLUDE_SOURCEMOD_BASEFUNCTION_H_ #include +#include namespace sp { using namespace SourcePawn; class PluginRuntime; +class PluginContext; class CompiledFunction; struct ParamInfo @@ -43,17 +45,18 @@ class ScriptedInvoker : public IPluginFunction ~ScriptedInvoker(); public: - virtual int PushCell(cell_t cell); - virtual int PushCellByRef(cell_t *cell, int flags); - virtual int PushFloat(float number); - virtual int PushFloatByRef(float *number, int flags); - virtual int PushArray(cell_t *inarray, unsigned int cells, int copyback); - virtual int PushString(const char *string); - virtual int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags); - virtual int Execute(cell_t *result); - virtual void Cancel(); - virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result); - virtual IPluginContext *GetParentContext(); + 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); + 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); @@ -83,13 +86,15 @@ class ScriptedInvoker : public IPluginFunction int SetError(int err); private: + Environment *env_; PluginRuntime *m_pRuntime; + PluginContext *context_; cell_t m_params[SP_MAX_EXEC_PARAMS]; ParamInfo m_info[SP_MAX_EXEC_PARAMS]; unsigned int m_curparam; int m_errorstate; funcid_t m_FnId; - char *full_name_; + ke::AutoArray full_name_; sp_public_t *public_; CompiledFunction *cc_function_; }; diff --git a/sourcepawn/jit/stack-frames.cpp b/sourcepawn/jit/stack-frames.cpp new file mode 100644 index 00000000..c14ca03e --- /dev/null +++ b/sourcepawn/jit/stack-frames.cpp @@ -0,0 +1,227 @@ +// 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 "environment.h" +#include "plugin-runtime.h" +#include "plugin-context.h" +#include "stack-frames.h" +#include "x86/frames-x86.h" +#include "compiled-function.h" + +using namespace ke; +using namespace sp; +using namespace SourcePawn; + +InvokeFrame::InvokeFrame(PluginContext *cx, cell_t entry_cip) + : prev_(Environment::get()->top()), + cx_(cx), + prev_exit_frame_(Environment::get()->exit_frame()), + entry_cip_(0), + entry_sp_(nullptr) +{ + Environment::get()->enterInvoke(this); +} + +InvokeFrame::~InvokeFrame() +{ + assert(Environment::get()->top() == this); + Environment::get()->leaveInvoke(); +} + +FrameIterator::FrameIterator() + : ivk_(Environment::get()->top()), + exit_frame_(Environment::get()->exit_frame()), + runtime_(nullptr), + sp_iter_(nullptr), + sp_stop_(nullptr), + function_cip_(kInvalidCip), + cip_(kInvalidCip), + pc_(nullptr) +{ + if (!ivk_) + return; + + nextInvokeFrame(); +} + +void +FrameIterator::nextInvokeFrame() +{ + assert(exit_frame_.exit_sp()); + sp_iter_ = exit_frame_.exit_sp(); + + // Inside an exit frame, the stack looks like this: + // .. C++ .. + // ----------- <-- entry_sp + // return addr to C++ + // entry cip for frame #0 + // alignment + // ----------- + // return addr to frame #0 + // entry cip for frame #1 + // alignment + // ----------- <-- exit sp + // saved regs + // return addr + // .. InvokeNativeBoundHelper() .. + // + // We are guaranteed to always have one frame. We subtract one frame from + // the entry sp so we hit the stopping point correctly. + assert(ivk_->entry_sp()); + assert(ke::IsAligned(sizeof(JitFrame), sizeof(intptr_t))); + sp_stop_ = ivk_->entry_sp() - (sizeof(JitFrame) / sizeof(intptr_t)); + assert(sp_stop_ >= sp_iter_); + + runtime_ = ivk_->cx()->runtime(); + function_cip_ = -1; + pc_ = nullptr; + cip_ = kInvalidCip; + + if (!exit_frame_.has_exit_native()) { + // We have an exit frame, but it's not for natives. automatically advance + // to the most recent scripted frame. + const JitExitFrameForHelper *exit = + JitExitFrameForHelper::FromExitSp(exit_frame_.exit_sp()); + exit_frame_ = ExitFrame(); + + // If we haven't compiled the function yet, but threw an error, then the + // return address will be null. + pc_ = exit->return_address; + assert(pc_ || exit->isCompileFunction()); + + // The function owning pc_ is in the previous frame. + const JitFrame *frame = exit->prev(); + function_cip_ = frame->function_cip; + sp_iter_ = reinterpret_cast(frame); + return; + } +} + +void +FrameIterator::Next() +{ + void *pc = nullptr; + if (exit_frame_.has_exit_native()) { + // If we're at an exit frame, the return address will yield the current pc. + const JitExitFrameForNative *exit = + JitExitFrameForNative::FromExitSp(exit_frame_.exit_sp()); + exit_frame_ = ExitFrame(); + + pc_ = exit->return_address; + cip_ = kInvalidCip; + + // The function owning pc_ is in the previous frame. + const JitFrame *frame = JitFrame::FromSp(sp_iter_); + function_cip_ = frame->function_cip; + return; + } + + if (sp_iter_ >= sp_stop_) { + // Jump to the next invoke frame. + exit_frame_ = ivk_->prev_exit_frame(); + ivk_ = ivk_->prev(); + if (!ivk_) + return; + + nextInvokeFrame(); + return; + } + + pc_ = JitFrame::FromSp(sp_iter_)->return_address; + assert(pc_); + + // Advance, and find the function cip the pc belongs to. + sp_iter_ = reinterpret_cast(JitFrame::FromSp(sp_iter_) + 1); + function_cip_ = JitFrame::FromSp(sp_iter_)->function_cip; + cip_ = kInvalidCip; +} + +void +FrameIterator::Reset() +{ + *this = FrameIterator(); +} + +cell_t +FrameIterator::findCip() const +{ + CompiledFunction *fn = runtime_->GetJittedFunctionByOffset(function_cip_); + if (!fn) + return 0; + + if (cip_ == kInvalidCip) { + if (pc_) + cip_ = fn->FindCipByPc(pc_); + else + cip_ = function_cip_; + } + return cip_; +} + +unsigned +FrameIterator::LineNumber() const +{ + cell_t cip = findCip(); + if (cip == kInvalidCip) + return 0; + + uint32_t line; + if (!runtime_->image()->LookupLine(cip, &line)) + return 0; + + return line; +} + +const char * +FrameIterator::FilePath() const +{ + cell_t cip = findCip(); + if (cip == kInvalidCip) + return runtime_->image()->LookupFile(function_cip_); + + return runtime_->image()->LookupFile(cip); +} + +const char * +FrameIterator::FunctionName() const +{ + assert(ivk_); + if (exit_frame_.has_exit_native()) { + uint32_t native_index = exit_frame_.exit_native(); + const sp_native_t *native = runtime_->GetNative(native_index); + if (!native) + return nullptr; + return native->name; + } + + return runtime_->image()->LookupFunction(function_cip_); +} + +bool +FrameIterator::IsNativeFrame() const +{ + return exit_frame_.has_exit_native(); +} + +bool +FrameIterator::IsScriptedFrame() const +{ + return !IsNativeFrame() && ivk_; +} + +IPluginContext * +FrameIterator::Context() const +{ + if (!ivk_) + return nullptr; + return ivk_->cx(); +} diff --git a/sourcepawn/jit/stack-frames.h b/sourcepawn/jit/stack-frames.h new file mode 100644 index 00000000..6269cb48 --- /dev/null +++ b/sourcepawn/jit/stack-frames.h @@ -0,0 +1,135 @@ +// 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_sourcepawn_vm_stack_frames_h_ +#define _include_sourcepawn_vm_stack_frames_h_ + +#include +#include + +namespace sp { + +using namespace SourcePawn; + +class PluginContext; +class PluginRuntime; + +// An ExitFrame represents the state of the most recent exit from VM state to +// the outside world. Because this transition is on a critical path, we declare +// exactly one ExitFrame and save/restore it in InvokeFrame(). Anytime we're in +// the VM, we are guaranteed to have an ExitFrame for each InvokeFrame(). +class ExitFrame +{ + public: + ExitFrame() + : exit_sp_(nullptr), + exit_native_(-1) + {} + + public: + const intptr_t *exit_sp() const { + return exit_sp_; + } + bool has_exit_native() const { + return exit_native_ != -1; + } + uint32_t exit_native() const { + assert(has_exit_native()); + return exit_native_; + } + + public: + static inline size_t offsetOfExitSp() { + return offsetof(ExitFrame, exit_sp_); + } + static inline size_t offsetOfExitNative() { + return offsetof(ExitFrame, exit_native_); + } + + private: + const intptr_t *exit_sp_; + int exit_native_; +}; + +// An InvokeFrame represents one activation of Execute2(). +class InvokeFrame +{ + public: + InvokeFrame(PluginContext *cx, cell_t cip); + ~InvokeFrame(); + + InvokeFrame *prev() const { + return prev_; + } + PluginContext *cx() const { + return cx_; + } + + const ExitFrame &prev_exit_frame() const { + return prev_exit_frame_; + } + const intptr_t *entry_sp() const { + return entry_sp_; + } + cell_t entry_cip() const { + return entry_cip_; + } + + public: + static inline size_t offsetOfEntrySp() { + return offsetof(InvokeFrame, entry_sp_); + } + + private: + InvokeFrame *prev_; + PluginContext *cx_; + ExitFrame prev_exit_frame_; + cell_t entry_cip_; + const intptr_t *entry_sp_; +}; + +class FrameIterator : public SourcePawn::IFrameIterator +{ + public: + FrameIterator(); + + bool Done() const KE_OVERRIDE { + return !ivk_; + } + void Next() KE_OVERRIDE; + void Reset() KE_OVERRIDE; + + bool IsNativeFrame() const KE_OVERRIDE; + bool IsScriptedFrame() const KE_OVERRIDE; + const char *FunctionName() const KE_OVERRIDE; + const char *FilePath() const KE_OVERRIDE; + unsigned LineNumber() const KE_OVERRIDE; + IPluginContext *Context() const KE_OVERRIDE; + + private: + void nextInvokeFrame(); + cell_t findCip() const; + + private: + InvokeFrame *ivk_; + ExitFrame exit_frame_; + PluginRuntime *runtime_; + const intptr_t *sp_iter_; + const intptr_t *sp_stop_; + cell_t function_cip_; + mutable cell_t cip_; + void *pc_; +}; + +} // namespace sp + +#endif // _include_sourcepawn_vm_stack_frames_h_ diff --git a/sourcepawn/jit/x86/code-stubs-x86.cpp b/sourcepawn/jit/x86/code-stubs-x86.cpp index ae6f5d71..0662c859 100644 --- a/sourcepawn/jit/x86/code-stubs-x86.cpp +++ b/sourcepawn/jit/x86/code-stubs-x86.cpp @@ -14,6 +14,7 @@ #include "code-stubs.h" #include "x86-utils.h" #include "jit_x86.h" +#include "environment.h" using namespace sp; using namespace SourcePawn; @@ -64,6 +65,12 @@ CodeStubs::CompileInvokeStub() // Align the stack. __ andl(esp, 0xfffffff0); + // Set up the last piece of the invoke frame. This lets us find the bounds + // of the call stack. + __ movl(eax, intptr_t(Environment::get())); + __ movl(eax, Operand(eax, Environment::offsetOfTopFrame())); + __ movl(Operand(eax, InvokeFrame::offsetOfEntrySp()), esp); + // Call into plugin (align the stack first). __ call(ecx); @@ -98,17 +105,11 @@ CodeStubs::CompileInvokeStub() __ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx __ jmp(&ret); - Label timeout; - __ bind(&timeout); - __ movl(eax, SP_ERROR_TIMEOUT); - __ jmp(&error); - invoke_stub_ = LinkCode(env_, masm); if (!invoke_stub_) return false; return_stub_ = reinterpret_cast(invoke_stub_) + error.offset(); - timeout_stub_ = reinterpret_cast(invoke_stub_) + timeout.offset(); return true; } diff --git a/sourcepawn/jit/x86/frames-x86.h b/sourcepawn/jit/x86/frames-x86.h new file mode 100644 index 00000000..35a90fc7 --- /dev/null +++ b/sourcepawn/jit/x86/frames-x86.h @@ -0,0 +1,83 @@ +// vim: set ts=8 sts=2 sw=2 tw=99 et: +// +// 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. +// +// SourcePawn is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SourcePawn. If not, see . +#ifndef _include_sourcepawn_jit_frames_x86_h_ +#define _include_sourcepawn_jit_frames_x86_h_ + +#include + +namespace sp { + +using namespace SourcePawn; + +class PluginContext; + +// This is the layout of the stack in between each scripted function call. +struct JitFrame +{ + intptr_t align0; + intptr_t align1; + ucell_t function_cip; + void *return_address; + + static inline const JitFrame *FromSp(const intptr_t *sp) { + return reinterpret_cast(sp); + } +}; + +// When we're about to call a native, the stack pointer we store in the exit +// frame is such that (sp + sizeof(JitExitFrameForNative)) conforms to this +// structure. +// +// Note that it looks reversed compared to JitFrame because we capture the sp +// before saving registers and pushing arguments. +struct JitExitFrameForNative +{ + void *return_address; + PluginContext *cx; + union { + uint32_t native_index; + SPVM_NATIVE_FUNC fn; + } arg; + const cell_t *params; + cell_t saved_alt; + + static inline const JitExitFrameForNative *FromExitSp(const intptr_t *exit_sp) { + return reinterpret_cast( + reinterpret_cast(exit_sp) - sizeof(JitExitFrameForNative)); + } +}; + +// Unlke native frames, the exit_sp for these is created at the base address. +struct JitExitFrameForHelper +{ + void *return_address; + + static inline const JitExitFrameForHelper *FromExitSp(const intptr_t *exit_sp) { + return reinterpret_cast(exit_sp); + } + + bool isCompileFunction() const { + return !!return_address; + } + const JitFrame *prev() const { + return reinterpret_cast(this + 1); + } +}; + +} // namespace sp + +#endif // _include_sourcepawn_jit_frames_x86_h_ diff --git a/sourcepawn/jit/x86/jit_x86.cpp b/sourcepawn/jit/x86/jit_x86.cpp index b0eb44ea..33dab470 100644 --- a/sourcepawn/jit/x86/jit_x86.cpp +++ b/sourcepawn/jit/x86/jit_x86.cpp @@ -236,6 +236,9 @@ Compiler::emit(int *errp) // an opcode, we bind its corresponding label. __ bind(&jump_map_[cip_ - codeseg]); + // Save the start of the opcode for emitCipMap(). + op_cip_ = cip_; + OPCODE op = (OPCODE)readCell(); if (!emitOp(op) || error_ != SP_ERROR_NONE) { *errp = (error_ == SP_ERROR_NONE) ? SP_ERROR_OUT_OF_MEMORY : error_; @@ -244,6 +247,17 @@ Compiler::emit(int *errp) } emitCallThunks(); + + // For each backward jump, emit a little thunk so we can exit from a timeout. + // Track the offset of where the thunk is, so the watchdog timer can patch it. + for (size_t i = 0; i < backward_jumps_.length(); i++) { + BackwardJump &jump = backward_jumps_[i]; + jump.timeout_offset = masm.pc(); + __ call(&throw_timeout_); + emitCipMapping(jump.cip); + } + + // This has to come last. emitErrorPaths(); uint8_t *code = LinkCode(env_, masm); @@ -255,44 +269,69 @@ Compiler::emit(int *errp) AutoPtr> edges( new FixedArray(backward_jumps_.length())); for (size_t i = 0; i < backward_jumps_.length(); i++) { - edges->at(i).offset = backward_jumps_[i]; - edges->at(i).disp32 = *reinterpret_cast(code + edges->at(i).offset - 4); + const BackwardJump &jump = backward_jumps_[i]; + edges->at(i).offset = jump.pc; + edges->at(i).disp32 = int32_t(jump.timeout_offset) - int32_t(jump.pc); } - return new CompiledFunction(code, pcode_start_, edges.take()); + AutoPtr> cipmap( + new FixedArray(cip_map_.length())); + memcpy(cipmap->buffer(), cip_map_.buffer(), cip_map_.length() * sizeof(CipMapEntry)); + + return new CompiledFunction(code, masm.length(), pcode_start_, edges.take(), cipmap.take()); } -// Helpers for invoking context members. +// No exit frame - error code is returned directly. static int InvokePushTracker(PluginContext *cx, uint32_t amount) { return cx->pushTracker(amount); } +// No exit frame - error code is returned directly. static int InvokePopTrackerAndSetHeap(PluginContext *cx) { return cx->popTrackerAndSetHeap(); } +// Error code must be checked in the environment. static cell_t InvokeNativeHelper(PluginContext *cx, ucell_t native_idx, cell_t *params) { return cx->invokeNative(native_idx, params); } +// Error code must be checked in the environment. static cell_t InvokeBoundNativeHelper(PluginContext *cx, SPVM_NATIVE_FUNC fn, cell_t *params) { return cx->invokeBoundNative(fn, params); } +// No exit frame - error code is returned directly. static int InvokeGenerateFullArray(PluginContext *cx, uint32_t argc, cell_t *argv, int autozero) { return cx->generateFullArray(argc, argv, autozero); } +// Exit frame is a JitExitFrameForHelper. +static void +InvokeReportError(int err) +{ + Environment::get()->ReportError(err); +} + +// Exit frame is a JitExitFrameForHelper. This is a special function since we +// have to notify the watchdog timer that we're unblocked. +static void +InvokeReportTimeout() +{ + Environment::get()->watchdog()->NotifyTimeoutReceived(); + InvokeReportError(SP_ERROR_TIMEOUT); +} + bool Compiler::emitOp(OPCODE op) { @@ -450,8 +489,16 @@ Compiler::emitOp(OPCODE op) __ subl(tmp, dat); __ movl(Operand(frmAddr()), tmp); - // Align the stack to 16-bytes (each call adds 4 bytes). - __ subl(esp, 12); + // Store the function cip for stack traces. + __ push(pcode_start_); + + // Align the stack to 16-bytes (each call adds 8 bytes). + __ subl(esp, 8); +#if defined(DEBUG) + // Debug guards. + __ movl(Operand(esp, 0), 0xffaaee00); + __ movl(Operand(esp, 4), 0xffaaee04); +#endif break; case OP_IDXADDR_B: @@ -769,14 +816,14 @@ Compiler::emitOp(OPCODE op) // Guard against divide-by-zero. __ testl(divisor, divisor); - __ j(zero, &error_divide_by_zero_); + jumpOnError(zero, SP_ERROR_DIVIDE_BY_ZERO); // A more subtle case; -INT_MIN / -1 yields an overflow exception. Label ok; __ cmpl(divisor, -1); __ j(not_equal, &ok); __ cmpl(dividend, 0x80000000); - __ j(equal, &error_integer_overflow_); + jumpOnError(equal, SP_ERROR_INTEGER_OVERFLOW); __ bind(&ok); // Now we can actually perform the divide. @@ -1078,13 +1125,13 @@ Compiler::emitOp(OPCODE op) if (amount > 0) { // Check if the stack went beyond the stack top - usually a compiler error. __ cmpl(stk, intptr_t(context_->memory() + context_->HeapSize())); - __ j(not_below, &error_stack_min_); + jumpOnError(not_below, SP_ERROR_STACKMIN); } else { // Check if the stack is going to collide with the heap. __ movl(tmp, Operand(hpAddr())); __ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN)); __ cmpl(stk, tmp); - __ j(below, &error_stack_low_); + jumpOnError(below, SP_ERROR_STACKLOW); } break; } @@ -1097,12 +1144,12 @@ Compiler::emitOp(OPCODE op) if (amount < 0) { __ cmpl(Operand(hpAddr()), context_->DataSize()); - __ j(below, &error_heap_min_); + jumpOnError(below, SP_ERROR_HEAPMIN); } else { __ movl(tmp, Operand(hpAddr())); __ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN)); __ cmpl(tmp, stk); - __ j(above, &error_heap_low_); + jumpOnError(above, SP_ERROR_HEAPLOW); } break; } @@ -1114,7 +1161,7 @@ Compiler::emitOp(OPCODE op) return false; if (target->bound()) { __ jmp32(target); - backward_jumps_.append(masm.pc()); + backward_jumps_.append(BackwardJump(masm.pc(), op_cip_)); } else { __ jmp(target); } @@ -1131,7 +1178,7 @@ Compiler::emitOp(OPCODE op) __ testl(pri, pri); if (target->bound()) { __ j32(cc, target); - backward_jumps_.append(masm.pc()); + backward_jumps_.append(BackwardJump(masm.pc(), op_cip_)); } else { __ j(cc, target); } @@ -1152,7 +1199,7 @@ Compiler::emitOp(OPCODE op) __ cmpl(pri, alt); if (target->bound()) { __ j32(cc, target); - backward_jumps_.append(masm.pc()); + backward_jumps_.append(BackwardJump(masm.pc(), op_cip_)); } else { __ j(cc, target); } @@ -1171,7 +1218,7 @@ Compiler::emitOp(OPCODE op) __ call(ExternalAddress((void *)InvokePushTracker)); __ addl(esp, 8); __ testl(eax, eax); - __ j(not_zero, &extern_error_); + jumpOnError(not_zero); __ pop(alt); __ pop(pri); @@ -1189,31 +1236,32 @@ Compiler::emitOp(OPCODE op) __ call(ExternalAddress((void *)InvokePopTrackerAndSetHeap)); __ addl(esp, 4); __ testl(eax, eax); - __ j(not_zero, &extern_error_); + jumpOnError(not_zero); __ pop(alt); __ pop(pri); break; } + // This opcode is used to note where line breaks occur. We don't support + // live debugging, and if we did, we could build this map from the lines + // table. So we don't generate any code here. case OP_BREAK: - { - cell_t cip = uintptr_t(cip_ - 1) - uintptr_t(rt_->code().bytes); - __ movl(Operand(cipAddr()), cip); break; - } + // This should never be hit. case OP_HALT: __ align(16); __ movl(pri, readCell()); - __ jmp(&extern_error_); + __ testl(eax, eax); + jumpOnError(not_zero); break; case OP_BOUNDS: { cell_t value = readCell(); __ cmpl(eax, value); - __ j(above, &error_bounds_); + jumpOnError(above, SP_ERROR_ARRAY_BOUNDS); break; } @@ -1281,7 +1329,7 @@ Compiler::emitCheckAddress(Register reg) { // Check if we're in memory bounds. __ cmpl(reg, context_->HeapSize()); - __ j(not_below, &error_memaccess_); + jumpOnError(not_below, SP_ERROR_MEMACCESS); // Check if we're in the invalid region between hp and sp. Label done; @@ -1289,7 +1337,7 @@ Compiler::emitCheckAddress(Register reg) __ j(below, &done); __ lea(tmp, Operand(dat, reg, NoScale)); __ cmpl(tmp, stk); - __ j(below, &error_memaccess_); + jumpOnError(below, SP_ERROR_MEMACCESS); __ bind(&done); } @@ -1308,7 +1356,7 @@ Compiler::emitGenArray(bool autozero) __ movl(Operand(hpAddr()), alt); __ addl(alt, dat); __ cmpl(alt, stk); - __ j(not_below, &error_heap_low_); + jumpOnError(not_below, SP_ERROR_HEAPLOW); __ shll(tmp, 2); __ push(tmp); @@ -1318,7 +1366,7 @@ Compiler::emitGenArray(bool autozero) __ pop(tmp); __ shrl(tmp, 2); __ testl(eax, eax); - __ j(not_zero, &extern_error_); + jumpOnError(not_zero); if (autozero) { // Note - tmp is ecx and still intact. @@ -1347,7 +1395,7 @@ Compiler::emitGenArray(bool autozero) __ pop(tmp); __ testl(eax, eax); - __ j(not_zero, &extern_error_); + jumpOnError(not_zero); // Move tmp back to pri, remove pushed args. __ movl(pri, tmp); @@ -1367,25 +1415,6 @@ Compiler::emitCall() return false; } - // eax = context - // ecx = rp - __ movl(eax, intptr_t(rt_->GetBaseContext())); - __ movl(ecx, Operand(eax, PluginContext::offsetOfRp())); - - // Check if the return stack is used up. - __ cmpl(ecx, SP_MAX_RETURN_STACK); - __ j(not_below, &error_stack_low_); - - // Add to the return stack. - uintptr_t cip = uintptr_t(cip_ - 2) - uintptr_t(rt_->code().bytes); - __ movl(Operand(eax, ecx, ScaleFour, PluginContext::offsetOfRstkCips()), cip); - - // Increment the return stack pointer. - __ addl(Operand(eax, PluginContext::offsetOfRp()), 1); - - // Store the CIP of the function we're about to call. - __ movl(Operand(cipAddr()), offset); - CompiledFunction *fun = rt_->GetJittedFunctionByOffset(offset); if (!fun) { // Need to emit a delayed thunk. @@ -1398,12 +1427,8 @@ Compiler::emitCall() __ call(ExternalAddress(fun->GetEntryAddress())); } - // Restore the last cip. - __ movl(Operand(cipAddr()), cip); - - // Mark us as leaving the last frame. - __ movl(tmp, intptr_t(rt_->GetBaseContext())); - __ subl(Operand(tmp, PluginContext::offsetOfRp()), 1); + // Map the return address to the cip that started this call. + emitCipMapping(op_cip_); return true; } @@ -1415,15 +1440,27 @@ Compiler::emitCallThunks() Label error; __ bind(&thunk->call); - // Huge hack - get the return address, since that is the call that we - // need to patch. + + // Get the return address, since that is the call that we need to patch. __ movl(eax, Operand(esp, 0)); + // Push an OP_PROC frame as if we already called the function. This helps + // error reporting. + __ push(thunk->pcode_offset); + __ subl(esp, 8); + + // Create the exit frame, then align the stack. + __ push(0); + __ movl(ecx, intptr_t(&Environment::get()->exit_frame())); + __ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1); + __ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp); + // We need to push 4 arguments, and one of them will need an extra word - // on the stack. Allocate a big block so we're aligned, subtracting - // 4 because we got here via a call. + // on the stack. Allocate a big block so we're aligned. + // + // Note: we add 12 since the push above misaligned the stack. static const size_t kStackNeeded = 5 * sizeof(void *); - static const size_t kStackReserve = ke::Align(kStackNeeded, 16) - sizeof(void *); + static const size_t kStackReserve = ke::Align(kStackNeeded, 16) + 3 * sizeof(void *); __ subl(esp, kStackReserve); // Set arguments. @@ -1435,14 +1472,10 @@ Compiler::emitCallThunks() __ call(ExternalAddress((void *)CompileFromThunk)); __ movl(edx, Operand(esp, 4 * sizeof(void *))); - __ addl(esp, kStackReserve); + __ addl(esp, kStackReserve + 4 * sizeof(void *)); // Drop the exit frame and fake frame. __ testl(eax, eax); - __ j(not_zero, &error); + jumpOnError(not_zero); __ jmp(edx); - - __ bind(&error); - __ movl(Operand(cipAddr()), thunk->pcode_offset); - __ jmp(ExternalAddress(env_->stubs()->ReturnStub())); } } @@ -1482,18 +1515,23 @@ Compiler::emitNativeCall(OPCODE op) __ subl(stk, 4); } + // Create the exit frame. This is a JitExitFrameForNative, so everything we + // push up to the return address of the call instruction is reflected in + // that structure. + __ movl(eax, intptr_t(&Environment::get()->exit_frame())); + __ movl(Operand(eax, ExitFrame::offsetOfExitNative()), native_index); + __ movl(Operand(eax, ExitFrame::offsetOfExitSp()), esp); + // Save registers. __ push(edx); // Push the last parameter for the C++ function. __ push(stk); - __ movl(eax, intptr_t(rt_->GetBaseContext())); - __ movl(Operand(eax, PluginContext::offsetOfLastNative()), native_index); - // Relocate our absolute stk to be dat-relative, and update the context's // view. __ subl(stk, dat); + __ movl(eax, intptr_t(context_)); __ movl(Operand(eax, PluginContext::offsetOfSp()), stk); const sp_native_t *native = rt_->GetNative(native_index); @@ -1512,11 +1550,14 @@ Compiler::emitNativeCall(OPCODE op) __ call(ExternalAddress((void *)InvokeBoundNativeHelper)); } - // Check for errors. - __ movl(ecx, intptr_t(rt_->GetBaseContext())); - __ movl(ecx, Operand(ecx, PluginContext::offsetOfNativeError())); - __ testl(ecx, ecx); - __ j(not_zero, &extern_error_); + // Map the return address to the cip that initiated this call. + emitCipMapping(op_cip_); + + // Check for errors. Note we jump directly to the return stub since the + // error has already been reported. + __ movl(ecx, intptr_t(Environment::get())); + __ cmpl(Operand(ecx, Environment::offsetOfExceptionCode()), 0); + __ j(not_zero, &return_reported_error_); // Restore local state. __ addl(stk, dat); @@ -1631,16 +1672,6 @@ Compiler::emitSwitch() return true; } -void -Compiler::emitErrorPath(Label *dest, int code) -{ - if (dest->used()) { - __ bind(dest); - __ movl(eax, code); - __ jmp(ExternalAddress(env_->stubs()->ReturnStub())); - } -} - void Compiler::emitFloatCmp(ConditionCode cc) { @@ -1691,22 +1722,112 @@ Compiler::emitFloatCmp(ConditionCode cc) __ addl(stk, 8); } +void +Compiler::jumpOnError(ConditionCode cc, int err) +{ + // Note: we accept 0 for err. In this case we expect the error to be in eax. + { + ErrorPath path(op_cip_, err); + error_paths_.append(path); + } + + ErrorPath &path = error_paths_.back(); + __ j(cc, &path.label); +} + void Compiler::emitErrorPaths() { - emitErrorPath(&error_divide_by_zero_, SP_ERROR_DIVIDE_BY_ZERO); - emitErrorPath(&error_stack_low_, SP_ERROR_STACKLOW); - emitErrorPath(&error_stack_min_, SP_ERROR_STACKMIN); - emitErrorPath(&error_bounds_, SP_ERROR_ARRAY_BOUNDS); - emitErrorPath(&error_memaccess_, SP_ERROR_MEMACCESS); - emitErrorPath(&error_heap_low_, SP_ERROR_HEAPLOW); - emitErrorPath(&error_heap_min_, SP_ERROR_HEAPMIN); - emitErrorPath(&error_integer_overflow_, SP_ERROR_INTEGER_OVERFLOW); + // For each path that had an error check, bind it to an error routine and + // add it to the cip map. What we'll get is something like: + // + // cmp dividend, 0 + // jz error_thunk_0 + // + // error_thunk_0: + // call integer_overflow + // + // integer_overflow: + // mov eax, SP_ERROR_DIVIDE_BY_ZERO + // jmp report_error + // + // report_error: + // create exit frame + // push eax + // call InvokeReportError(int err) + // + for (size_t i = 0; i < error_paths_.length(); i++) { + ErrorPath &path = error_paths_[i]; - if (extern_error_.used()) { - __ bind(&extern_error_); - __ movl(eax, intptr_t(rt_->GetBaseContext())); - __ movl(eax, Operand(eax, PluginContext::offsetOfNativeError())); + // If there's no error code, it should be in eax. Otherwise we'll jump to + // a path that sets eax to a hardcoded value. + __ bind(&path.label); + if (path.err == 0) + __ call(&report_error_); + else + __ call(&throw_error_code_[path.err]); + + emitCipMapping(path.cip); + } + + emitThrowPathIfNeeded(SP_ERROR_DIVIDE_BY_ZERO); + emitThrowPathIfNeeded(SP_ERROR_STACKLOW); + emitThrowPathIfNeeded(SP_ERROR_STACKMIN); + emitThrowPathIfNeeded(SP_ERROR_ARRAY_BOUNDS); + emitThrowPathIfNeeded(SP_ERROR_MEMACCESS); + emitThrowPathIfNeeded(SP_ERROR_HEAPLOW); + emitThrowPathIfNeeded(SP_ERROR_HEAPMIN); + emitThrowPathIfNeeded(SP_ERROR_INTEGER_OVERFLOW); + + if (report_error_.used()) { + __ bind(&report_error_); + + // Create the exit frame. We always get here through a call from the opcode + // (and always via an out-of-line thunk). + __ movl(ecx, intptr_t(&Environment::get()->exit_frame())); + __ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1); + __ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp); + + // Since the return stub wipes out the stack, we don't need to subl after + // the call. + __ push(eax); + __ call(ExternalAddress((void *)InvokeReportError)); + __ jmp(ExternalAddress(env_->stubs()->ReturnStub())); + } + + // We get here if we know an exception is already pending. + if (return_reported_error_.used()) { + __ bind(&return_reported_error_); + __ movl(eax, intptr_t(Environment::get())); + __ movl(eax, Operand(eax, Environment::offsetOfExceptionCode())); + __ jmp(ExternalAddress(env_->stubs()->ReturnStub())); + } + + // The timeout uses a special stub. + if (throw_timeout_.used()) { + __ bind(&throw_timeout_); + + // Create the exit frame. + __ movl(ecx, intptr_t(&Environment::get()->exit_frame())); + __ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1); + __ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp); + + // Since the return stub wipes out the stack, we don't need to subl after + // the call. + __ call(ExternalAddress((void *)InvokeReportTimeout)); __ jmp(ExternalAddress(env_->stubs()->ReturnStub())); } } + +void +Compiler::emitThrowPathIfNeeded(int err) +{ + assert(err < SP_MAX_ERROR_CODES); + + if (!throw_error_code_[err].used()) + return; + + __ bind(&throw_error_code_[err]); + __ movl(eax, err); + __ jmp(&report_error_); +} diff --git a/sourcepawn/jit/x86/jit_x86.h b/sourcepawn/jit/x86/jit_x86.h index 5bf03a45..1ad00b62 100644 --- a/sourcepawn/jit/x86/jit_x86.h +++ b/sourcepawn/jit/x86/jit_x86.h @@ -33,6 +33,36 @@ class LegacyImage; class Environment; class CompiledFunction; +struct ErrorPath +{ + SilentLabel label; + const cell_t *cip; + int err; + + ErrorPath(const cell_t *cip, int err) + : cip(cip), + err(err) + {} + ErrorPath() + {} +}; + +struct BackwardJump { + // The pc at the jump instruction (i.e. after it). + uint32_t pc; + // The cip of the jump. + const cell_t *cip; + // The offset of the timeout thunk. This is filled in at the end. + uint32_t timeout_offset; + + BackwardJump() + {} + BackwardJump(uint32_t pc, const cell_t *cip) + : pc(pc), + cip(cip) + {} +}; + #define JIT_INLINE_ERRORCHECKS (1<<0) #define JIT_INLINE_NATIVES (1<<1) #define STACK_MARGIN 64 //8 parameters of safety, I guess @@ -42,21 +72,9 @@ class CompiledFunction; #define sDIMEN_MAX 5 //this must mirror what the compiler has. -typedef struct funcinfo_s -{ - unsigned int magic; - unsigned int index; -} funcinfo_t; - -typedef struct functracker_s -{ - unsigned int num_functions; - unsigned int code_size; -} functracker_t; - struct CallThunk { - Label call; + SilentLabel call; cell_t pcode_offset; CallThunk(cell_t pcode_offset) @@ -89,10 +107,9 @@ class Compiler void emitErrorPath(Label *dest, int code); void emitErrorPaths(); void emitFloatCmp(ConditionCode cc); + void jumpOnError(ConditionCode cc, int err = 0); + void emitThrowPathIfNeeded(int err); - ExternalAddress cipAddr() { - return ExternalAddress(context_->addressOfCip()); - } ExternalAddress hpAddr() { return ExternalAddress(context_->addressOfHp()); } @@ -100,6 +117,16 @@ class Compiler return ExternalAddress(context_->addressOfFrm()); } + // Map a return address (i.e. an exit point from a function) to its source + // cip. This lets us avoid tracking the cip during runtime. These are + // sorted by definition since we assemble and emit in forward order. + void emitCipMapping(const cell_t *cip) { + CipMapEntry entry; + entry.cipoffs = uintptr_t(cip) - uintptr_t(code_start_); + entry.pcoffs = masm.pc(); + cip_map_.append(entry); + } + private: AssemblerX86 masm; Environment *env_; @@ -110,20 +137,19 @@ class Compiler uint32_t pcode_start_; const cell_t *code_start_; const cell_t *cip_; + const cell_t *op_cip_; const cell_t *code_end_; Label *jump_map_; - ke::Vector backward_jumps_; + ke::Vector backward_jumps_; - // Errors - Label error_bounds_; - Label error_heap_low_; - Label error_heap_min_; - Label error_stack_low_; - Label error_stack_min_; - Label error_divide_by_zero_; - Label error_memaccess_; - Label error_integer_overflow_; - Label extern_error_; + ke::Vector cip_map_; + + // Errors. + ke::Vector error_paths_; + Label throw_timeout_; + Label throw_error_code_[SP_MAX_ERROR_CODES]; + Label report_error_; + Label return_reported_error_; ke::Vector thunks_; //:TODO: free };