Merge pull request #285 from alliedmodders/frames

Implement a new stack and error handling model for the SourcePawn VM.
This commit is contained in:
David Anderson 2015-03-04 23:45:59 -08:00
commit 715a51d01f
51 changed files with 2085 additions and 1766 deletions

View File

@ -218,6 +218,7 @@ ResultType ConCmdManager::DispatchClientCommand(int client, const char *cmd, int
hook->pf->PushCell(args); hook->pf->PushCell(args);
cell_t tempres = result; cell_t tempres = result;
if (hook->pf->Execute(&tempres) == SP_ERROR_NONE) if (hook->pf->Execute(&tempres) == SP_ERROR_NONE)
{ {
if (tempres > result) if (tempres > result)

View File

@ -62,7 +62,6 @@ binary.sources += [
'PluginSys.cpp', 'PluginSys.cpp',
'HandleSys.cpp', 'HandleSys.cpp',
'NativeOwner.cpp', 'NativeOwner.cpp',
'NativeInvoker.cpp',
'ExtensionSys.cpp', 'ExtensionSys.cpp',
'DebugReporter.cpp', 'DebugReporter.cpp',
'Database.cpp', 'Database.cpp',

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 : * vim: set ts=4 sw=4 tw=99 noet :
* ============================================================================= * =============================================================================
* SourceMod * SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * 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 DebugReport::_GetPluginIndex(IPluginContext *ctx)
{ {
int id = 1; int id = 1;
@ -199,3 +154,46 @@ int DebugReport::_GetPluginIndex(IPluginContext *ctx)
return pluginsys->GetPluginCount() + 1; 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 = "<unknown function>";
if (iter.IsNativeFrame()) {
g_Logger.LogError("[SM] [%d] %s", index, fn);
continue;
}
if (iter.IsScriptedFrame()) {
const char *file = iter.FilePath();
if (!file)
file = "<unknown>";
g_Logger.LogError("[SM] [%d] Line %d, %s::%s()",
index,
iter.LineNumber(),
file,
fn);
}
}
}

View File

@ -42,7 +42,7 @@ class DebugReport :
public: // SMGlobalClass public: // SMGlobalClass
void OnSourceModAllInitialized(); void OnSourceModAllInitialized();
public: // IDebugListener public: // IDebugListener
void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error); void ReportError(const IErrorReport &report, IFrameIterator &iter);
void OnDebugSpew(const char *msg, ...); void OnDebugSpew(const char *msg, ...);
public: public:
void GenerateError(IPluginContext *ctx, cell_t func_idx, int err, const char *message, ...); void GenerateError(IPluginContext *ctx, cell_t func_idx, int err, const char *message, ...);

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* 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<Native> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_
#define _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_
#include <sp_vm_api.h>
#include <INativeInvoker.h>
#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_ */

View File

@ -1175,13 +1175,9 @@ bool CPluginManager::FindOrRequirePluginDeps(CPlugin *pPlugin, char *error, size
if ((pFunc=pBase->GetFunctionByName(buffer))) if ((pFunc=pBase->GetFunctionByName(buffer)))
{ {
cell_t res; cell_t res;
pFunc->Execute(&res); if (pFunc->Execute(&res) != SP_ERROR_NONE) {
if (pPlugin->GetBaseContext()->GetLastNativeError() != SP_ERROR_NONE)
{
if (error) if (error)
{
smcore.Format(error, maxlength, "Fatal error during initializing plugin load"); smcore.Format(error, maxlength, "Fatal error during initializing plugin load");
}
return false; return false;
} }
} }
@ -1303,13 +1299,9 @@ bool CPluginManager::LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass
if ((pFunc = pBase->GetFunctionByName(buffer)) != NULL) if ((pFunc = pBase->GetFunctionByName(buffer)) != NULL)
{ {
cell_t res; cell_t res;
pFunc->Execute(&res); if (pFunc->Execute(&res) != SP_ERROR_NONE) {
if (pPlugin->GetBaseContext()->GetLastNativeError() != SP_ERROR_NONE)
{
if (error) if (error)
{
smcore.Format(error, maxlength, "Fatal error during plugin initialization (ext req)"); smcore.Format(error, maxlength, "Fatal error during plugin initialization (ext req)");
}
return false; return false;
} }
} }

View File

@ -150,11 +150,12 @@ static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024]; char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); size_t len;
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{ {
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 */ /* 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); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024]; char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); size_t len;
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{ {
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 */ /* 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]); g_pSM->SetGlobalTarget(params[1]);
char buffer[256]; char buffer[256];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); size_t len;
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->ClientCommand(pPlayer->GetEdict(), buffer); engine->ClientCommand(pPlayer->GetEdict(), buffer);
@ -240,11 +243,11 @@ static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(params[1]); g_pSM->SetGlobalTarget(params[1]);
char buffer[256]; 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); engine->FakeClientCommand(pPlayer->GetEdict(), buffer);
@ -258,11 +261,13 @@ static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params)
/* Build the format string */ /* Build the format string */
char buffer[1024]; char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 2); size_t len;
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{ {
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 */ /* If we're printing to the server, shortcut out */

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 : * vim: set ts=4 sw=4 tw=99 noet :
* ============================================================================= * =============================================================================
* SourceMod * SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * 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->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); {
DetectExceptions eh(pContext);
if (pContext->GetLastNativeError() == SP_ERROR_NONE) g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
{ if (eh.HasException())
pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", buffer); return 0;
} }
pContext->ReportError("%s", buffer);
return 0; return 0;
} }
@ -388,16 +389,16 @@ static cell_t SetFailState(IPluginContext *pContext, const cell_t *params)
{ {
char buffer[2048]; char buffer[2048];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1); {
if (pContext->GetLastNativeError() != SP_ERROR_NONE) DetectExceptions eh(pContext);
{ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
pPlugin->SetErrorState(Plugin_Failed, "%s", str); if (eh.HasException()) {
return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "Formatting error (%s)", str); pPlugin->SetErrorState(Plugin_Failed, "%s", str);
} return 0;
else }
{
pPlugin->SetErrorState(Plugin_Failed, "%s", buffer); 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]; char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3); {
DetectExceptions eh(pContext);
if (pContext->GetLastNativeError() != SP_ERROR_NONE) g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3);
{ if (eh.HasException())
return 0; return 0;
} }
IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext()); IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext());
@ -536,14 +537,15 @@ static cell_t LogToFile(IPluginContext *pContext, const cell_t *params)
} }
char buffer[2048]; char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); {
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); DetectExceptions eh(pContext);
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
if (pContext->GetLastNativeError() != SP_ERROR_NONE) g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
{ if (eh.HasException()) {
fclose(fp); fclose(fp);
return 0; return 0;
} }
}
IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext()); IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext());
@ -569,14 +571,15 @@ static cell_t LogToFileEx(IPluginContext *pContext, const cell_t *params)
} }
char buffer[2048]; char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); {
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); DetectExceptions eh(pContext);
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
if (pContext->GetLastNativeError() != SP_ERROR_NONE) g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
{ if (eh.HasException()) {
fclose(fp); fclose(fp);
return 0; return 0;
} }
}
g_Logger.LogToOpenFile(fp, "%s", buffer); 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) if (sharesys->TestFeature(pContext->GetRuntime(), type, name) != FeatureStatus_Available)
{ {
char buffer[255]; char buffer[255];
char *msg = buffer; char *msg = buffer;
char default_message[255]; char default_message[255];
SMPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext()); SMPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext());
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3); DetectExceptions eh(pContext);
if (pContext->GetLastNativeError() != SP_ERROR_NONE || buffer[0] == '\0') g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3);
{ if (eh.HasException())
g_pSM->Format(default_message, sizeof(default_message), "Feature \"%s\" not available", name); buffer[0] = '\0';
msg = default_message;
} if (buffer[0] == '\0') {
pPlugin->SetErrorState(Plugin_Error, "%s", msg); g_pSM->Format(default_message, sizeof(default_message), "Feature \"%s\" not available", name);
return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", msg); msg = default_message;
} }
pPlugin->SetErrorState(Plugin_Error, "%s", msg);
if (!eh.HasException())
pContext->ReportFatalError("%s", msg);
return 0;
}
return 1; return 1;
} }

View File

@ -125,7 +125,6 @@ static cell_t smn_WritePackString(IPluginContext *pContext, const cell_t *params
HandleError herr; HandleError herr;
HandleSecurity sec; HandleSecurity sec;
IDataPack *pDataPack; IDataPack *pDataPack;
int err;
sec.pOwner = pContext->GetIdentity(); sec.pOwner = pContext->GetIdentity();
sec.pIdentity = g_pCoreIdent; sec.pIdentity = g_pCoreIdent;
@ -137,12 +136,7 @@ static cell_t smn_WritePackString(IPluginContext *pContext, const cell_t *params
} }
char *str; char *str;
if ((err=pContext->LocalToString(params[2], &str)) != SP_ERROR_NONE) pContext->LocalToString(params[2], &str);
{
pContext->ThrowNativeErrorEx(err, NULL);
return 0;
}
pDataPack->PackString(str); pDataPack->PackString(str);
return 1; return 1;

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 : * vim: set ts=4 sw=4 tw=99 noet:
* ============================================================================= * =============================================================================
* SourceMod * SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * 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]; 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; cell_t result = 0;
native->call->PushCell(pCaller->GetMyHandle()); native->call->PushCell(pCaller->GetMyHandle());
native->call->PushCell(params[0]); native->call->PushCell(params[0]);
int error; native->call->Invoke(&result);
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");
}
}
/* Restore everything from the stack if necessary */ /* Restore everything from the stack if necessary */
s_curnative = pSaveNative; s_curnative = pSaveNative;
@ -141,15 +135,15 @@ static cell_t ThrowNativeError(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[512]; 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"); DetectExceptions eh(pContext);
} else { g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
s_curcaller->ThrowNativeErrorEx(params[1], "%s", buffer); if (eh.HasException())
return 0;
} }
pContext->ReportError("%s", buffer);
return 0; return 0;
} }
@ -402,36 +396,32 @@ static cell_t FormatNativeString(IPluginContext *pContext, const cell_t *params)
char *format_buffer; char *format_buffer;
if (out_param) if (out_param)
{ s_curcaller->LocalToString(s_curparams[out_param], &output_buffer);
if ((err=s_curcaller->LocalToString(s_curparams[out_param], &output_buffer)) != SP_ERROR_NONE) else
{
return err;
}
} else {
pContext->LocalToString(params[6], &output_buffer); pContext->LocalToString(params[6], &output_buffer);
}
if (fmt_param) if (fmt_param)
{ s_curcaller->LocalToString(s_curparams[fmt_param], &format_buffer);
if ((err=s_curcaller->LocalToString(s_curparams[fmt_param], &format_buffer)) != SP_ERROR_NONE) else
{
return err;
}
} else {
pContext->LocalToString(params[7], &format_buffer); pContext->LocalToString(params[7], &format_buffer);
}
/* Get maximum length */ /* Get maximum length */
size_t maxlen = (size_t)params[4]; size_t maxlen = (size_t)params[4];
/* Do the format */ /* 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; cell_t *addr;
pContext->LocalToPhysAddr(params[5], &addr); pContext->LocalToPhysAddr(params[5], &addr);
*addr = (cell_t)written; *addr = (cell_t)written;
return s_curcaller->GetLastNativeError(); return SP_ERROR_NONE;
} }
//tee hee //tee hee

View File

@ -765,9 +765,12 @@ static cell_t sm_WriteFileLine(IPluginContext *pContext, const cell_t *params)
int arg = 3; int arg = 3;
char buffer[2048]; char buffer[2048];
smcore.atcprintf(buffer, sizeof(buffer), fmt, pContext, params, &arg); {
if (pContext->GetLastNativeError() != SP_ERROR_NONE) DetectExceptions eh(pContext);
return 0; smcore.atcprintf(buffer, sizeof(buffer), fmt, pContext, params, &arg);
if (eh.HasException())
return 0;
}
if (SystemFile *sysfile = file->AsSystemFile()) { if (SystemFile *sysfile = file->AsSystemFile()) {
fprintf(sysfile->fp(), "%s\n", buffer); 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[2], &buffer);
pContext->LocalToString(params[4], &fmt); pContext->LocalToString(params[4], &fmt);
smcore.atcprintf(path, sizeof(path), fmt, pContext, params, &arg); {
if (pContext->GetLastNativeError() != SP_ERROR_NONE) DetectExceptions eh(pContext);
return 0; 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); 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); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
size_t len;
char buffer[1024]; 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) 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); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024]; 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()); 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); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024]; 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()); IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext());
@ -900,9 +907,12 @@ static cell_t sm_LogToOpenFile(IPluginContext *pContext, const cell_t *params)
char buffer[2048]; char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); {
if (pContext->GetLastNativeError() != SP_ERROR_NONE) DetectExceptions eh(pContext);
return 0; g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext()); IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext());
g_Logger.LogToOpenFile(sysfile->fp(), "[%s] %s", pPlugin->GetFilename(), buffer); 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]; char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); {
if (pContext->GetLastNativeError() != SP_ERROR_NONE) DetectExceptions eh(pContext);
return 0; g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
g_Logger.LogToOpenFile(sysfile->fp(), "%s", buffer); g_Logger.LogToOpenFile(sysfile->fp(), "%s", buffer);
return 1; return 1;

View File

@ -564,6 +564,7 @@ static cell_t sm_CallFinish(IPluginContext *pContext, const cell_t *params)
pContext->LocalToPhysAddr(params[1], &result); pContext->LocalToPhysAddr(params[1], &result);
// Note: Execute() swallows exceptions, so this is okay.
if (s_pFunction) if (s_pFunction)
{ {
IPluginFunction *pFunction = s_pFunction; IPluginFunction *pFunction = s_pFunction;

View File

@ -1087,11 +1087,12 @@ static cell_t _ShowActivity(IPluginContext *pContext,
if (replyto == SM_REPLY_CONSOLE) if (replyto == SM_REPLY_CONSOLE)
{ {
g_pSM->SetGlobalTarget(client); 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); g_pSM->Format(message, sizeof(message), "%s%s\n", tag, buffer);
@ -1102,11 +1103,12 @@ static cell_t _ShowActivity(IPluginContext *pContext,
else else
{ {
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); 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); g_pSM->Format(message, sizeof(message), "%s%s\n", tag, buffer);
@ -1141,11 +1143,12 @@ static cell_t _ShowActivity(IPluginContext *pContext,
{ {
newsign = name; 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); g_pSM->Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer);
@ -1165,11 +1168,12 @@ static cell_t _ShowActivity(IPluginContext *pContext,
{ {
newsign = name; 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); 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->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 /* We don't display directly to the console because the chat text
@ -1227,11 +1231,11 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
else else
{ {
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); 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); g_pSM->Format(message, sizeof(message), "%s%s\n", tag, buffer);
@ -1266,11 +1270,12 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
{ {
newsign = name; 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); g_pSM->Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer);
@ -1290,11 +1295,12 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
{ {
newsign = name; 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); 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); g_pSM->SetGlobalTarget(client);
char buffer[256]; 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()) if (pPlayer->IsFakeClient())
@ -1387,11 +1393,11 @@ static cell_t KickClientEx(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(client); g_pSM->SetGlobalTarget(client);
char buffer[256]; 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); pPlayer->Kick(buffer);

View File

@ -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) if ((err=pContext->HeapAlloc(array_size, &amx_addr, &phys_addr)) != SP_ERROR_NONE)
{ {
pContext->ThrowNativeErrorEx(err, "Ran out of memory to sort"); pContext->ThrowNativeErrorEx(err, "Ran out of memory to sort");
return 0;
} }
g_CurStringArray = array; g_CurStringArray = array;
@ -282,12 +283,16 @@ struct sort_info
cell_t array_addr; cell_t array_addr;
cell_t *array_base; cell_t *array_base;
cell_t *array_remap; cell_t *array_remap;
ExceptionHandler *eh;
}; };
sort_info g_SortInfo; sort_info g_SortInfo;
int sort1d_amx_custom(const void *elem1, const void *elem2) 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 c1 = *(cell_t *)elem1;
cell_t c2 = *(cell_t *)elem2; 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(c2);
pf->PushCell(g_SortInfo.array_addr); pf->PushCell(g_SortInfo.array_addr);
pf->PushCell(g_SortInfo.hndl); pf->PushCell(g_SortInfo.hndl);
pf->Execute(&result); pf->Invoke(&result);
return result; return result;
} }
@ -317,22 +322,25 @@ static cell_t sm_SortCustom1D(IPluginContext *pContext, const cell_t *params)
sort_info oldinfo = g_SortInfo; sort_info oldinfo = g_SortInfo;
DetectExceptions eh(pContext);
g_SortInfo.hndl = params[4]; g_SortInfo.hndl = params[4];
g_SortInfo.array_addr = params[1]; g_SortInfo.array_addr = params[1];
g_SortInfo.array_remap = NULL; g_SortInfo.array_remap = NULL;
g_SortInfo.array_base = NULL; g_SortInfo.array_base = NULL;
g_SortInfo.pFunc = pFunction; g_SortInfo.pFunc = pFunction;
g_SortInfo.eh = &eh;
qsort(array, array_size, sizeof(cell_t), sort1d_amx_custom); qsort(array, array_size, sizeof(cell_t), sort1d_amx_custom);
g_SortInfo = oldinfo; g_SortInfo = oldinfo;
return 1; return 1;
} }
int sort2d_amx_custom(const void *elem1, const void *elem2) 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 c1 = *(cell_t *)elem1;
cell_t c2 = *(cell_t *)elem2; 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(c2_addr);
g_SortInfo.pFunc->PushCell(g_SortInfo.array_addr); g_SortInfo.pFunc->PushCell(g_SortInfo.array_addr);
g_SortInfo.pFunc->PushCell(g_SortInfo.hndl); g_SortInfo.pFunc->PushCell(g_SortInfo.hndl);
g_SortInfo.pFunc->Execute(&result); g_SortInfo.pFunc->Invoke(&result);
return result; return result;
} }
@ -378,9 +386,11 @@ static cell_t sm_SortCustom2D(IPluginContext *pContext, const cell_t *params)
sort_info oldinfo = g_SortInfo; sort_info oldinfo = g_SortInfo;
DetectExceptions eh(pContext);
g_SortInfo.pFunc = pFunction; g_SortInfo.pFunc = pFunction;
g_SortInfo.hndl = params[4]; g_SortInfo.hndl = params[4];
g_SortInfo.array_addr = params[1]; g_SortInfo.array_addr = params[1];
g_SortInfo.eh = &eh;
/** Same process as in strings, back up the old indices for later fixup */ /** Same process as in strings, back up the old indices for later fixup */
g_SortInfo.array_base = array; g_SortInfo.array_base = array;
@ -511,19 +521,23 @@ struct sort_infoADT
cell_t array_bsize; cell_t array_bsize;
Handle_t array_hndl; Handle_t array_hndl;
Handle_t hndl; Handle_t hndl;
ExceptionHandler *eh;
}; };
sort_infoADT g_SortInfoADT; sort_infoADT g_SortInfoADT;
int sort_adtarray_custom(const void *elem1, const void *elem2) int sort_adtarray_custom(const void *elem1, const void *elem2)
{ {
if (g_SortInfoADT.eh->HasException())
return 0;
cell_t result = 0; cell_t result = 0;
IPluginFunction *pf = g_SortInfoADT.pFunc; 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 *) 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(((cell_t) ((cell_t *) elem2 - g_SortInfoADT.array_base)) / g_SortInfoADT.array_bsize);
pf->PushCell(g_SortInfoADT.array_hndl); pf->PushCell(g_SortInfoADT.array_hndl);
pf->PushCell(g_SortInfoADT.hndl); pf->PushCell(g_SortInfoADT.hndl);
pf->Execute(&result); pf->Invoke(&result);
return result; return result;
} }
@ -552,11 +566,13 @@ static cell_t sm_SortADTArrayCustom(IPluginContext *pContext, const cell_t *para
sort_infoADT oldinfo = g_SortInfoADT; sort_infoADT oldinfo = g_SortInfoADT;
DetectExceptions eh(pContext);
g_SortInfoADT.pFunc = pFunction; g_SortInfoADT.pFunc = pFunction;
g_SortInfoADT.array_base = array; g_SortInfoADT.array_base = array;
g_SortInfoADT.array_bsize = (cell_t) blocksize; g_SortInfoADT.array_bsize = (cell_t) blocksize;
g_SortInfoADT.array_hndl = params[1]; g_SortInfoADT.array_hndl = params[1];
g_SortInfoADT.hndl = params[3]; g_SortInfoADT.hndl = params[3];
g_SortInfoADT.eh = &eh;
qsort(array, arraysize, blocksize * sizeof(cell_t), sort_adtarray_custom); qsort(array, arraysize, blocksize * sizeof(cell_t), sort_adtarray_custom);

View File

@ -1180,12 +1180,7 @@ reswitch:
{ {
CHECK_ARGS(0); CHECK_ARGS(0);
char *str; char *str;
int err; pCtx->LocalToString(params[arg], &str);
if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE)
{
pCtx->ThrowNativeErrorEx(err, "Could not deference string");
return 0;
}
AddString(&buf_p, llen, str, width, prec); AddString(&buf_p, llen, str, width, prec);
arg++; arg++;
break; break;

View File

@ -194,7 +194,6 @@ static cell_t smn_BfWriteString(IPluginContext *pCtx, const cell_t *params)
HandleError herr; HandleError herr;
HandleSecurity sec; HandleSecurity sec;
bf_write *pBitBuf; bf_write *pBitBuf;
int err;
sec.pOwner = NULL; sec.pOwner = NULL;
sec.pIdentity = g_pCoreIdent; sec.pIdentity = g_pCoreIdent;
@ -206,11 +205,7 @@ static cell_t smn_BfWriteString(IPluginContext *pCtx, const cell_t *params)
} }
char *str; char *str;
if ((err=pCtx->LocalToString(params[2], &str)) != SP_ERROR_NONE) pCtx->LocalToString(params[2], &str);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
pBitBuf->WriteString(str); pBitBuf->WriteString(str);

View File

@ -909,11 +909,12 @@ static cell_t sm_ServerCommandEx(IPluginContext *pContext, const cell_t *params)
g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024]; char buffer[1024];
size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 3); size_t len;
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{ {
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 */ /* 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]); g_SourceMod.SetGlobalTarget(params[1]);
char buffer[256]; 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); g_HL2.AddToFakeCliCmdQueue(params[1], GetPlayerUserId(pPlayer->GetEdict()), buffer);

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 : * vim: set ts=4 sw=4 tw=99 noet :
* ============================================================================= * =============================================================================
* SourceMod * SourceMod
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved. * 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); g_SourceMod.SetGlobalTarget(client);
char buffer[192]; 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)) 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); g_SourceMod.SetGlobalTarget(client);
char buffer[192]; 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)) 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); g_SourceMod.SetGlobalTarget(client);
char buffer[192]; 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)) if (!g_HL2.HintTextMsg(client, buffer))

View File

@ -1,5 +1,5 @@
/** /**
* vim: set ts=4 : * vim: set ts=4 sw=4 tw=99 noet:
* ============================================================================= * =============================================================================
* SourceMod * SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * 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.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); 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.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) if (params[2] == -1)

View File

@ -52,13 +52,8 @@
// Assumes message field name is param 2, gets as strField // Assumes message field name is param 2, gets as strField
#define GET_FIELD_NAME_OR_ERR() \ #define GET_FIELD_NAME_OR_ERR() \
int err; \
char *strField; \ char *strField; \
if ((err=pCtx->LocalToString(params[2], &strField)) != SP_ERROR_NONE) \ pCtx->LocalToString(params[2], &strField);
{ \
pCtx->ThrowNativeErrorEx(err, NULL); \
return 0; \
}
static cell_t smn_PbReadInt(IPluginContext *pCtx, const cell_t *params) 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(); GET_FIELD_NAME_OR_ERR();
char *strValue; char *strValue;
if ((err=pCtx->LocalToString(params[3], &strValue)) != SP_ERROR_NONE) pCtx->LocalToString(params[3], &strValue);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
int index = params[0] >= 4 ? params[4] : -1; int index = params[0] >= 4 ? params[4] : -1;
if (index < 0) if (index < 0)
@ -418,11 +409,7 @@ static cell_t smn_PbSetColor(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *clrParams; cell_t *clrParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &clrParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &clrParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
Color clr( Color clr(
clrParams[0], clrParams[0],
@ -455,11 +442,7 @@ static cell_t smn_PbSetAngle(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *angParams; cell_t *angParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &angParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &angParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
QAngle ang( QAngle ang(
sp_ctof(angParams[0]), sp_ctof(angParams[0]),
@ -491,11 +474,7 @@ static cell_t smn_PbSetVector(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *vecParams; cell_t *vecParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &vecParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
Vector vec( Vector vec(
sp_ctof(vecParams[0]), sp_ctof(vecParams[0]),
@ -527,11 +506,7 @@ static cell_t smn_PbSetVector2D(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *vecParams; cell_t *vecParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &vecParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
Vector2D vec( Vector2D vec(
sp_ctof(vecParams[0]), sp_ctof(vecParams[0]),
@ -602,11 +577,7 @@ static cell_t smn_PbAddString(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
char *strValue; char *strValue;
if ((err=pCtx->LocalToString(params[3], &strValue)) != SP_ERROR_NONE) pCtx->LocalToString(params[3], &strValue);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
if (!msg->AddString(strField, 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(); GET_FIELD_NAME_OR_ERR();
cell_t *clrParams; cell_t *clrParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &clrParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &clrParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
Color clr( Color clr(
clrParams[0], clrParams[0],
@ -648,11 +615,7 @@ static cell_t smn_PbAddAngle(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *angParams; cell_t *angParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &angParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &angParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
QAngle ang( QAngle ang(
sp_ctof(angParams[0]), sp_ctof(angParams[0]),
@ -673,11 +636,7 @@ static cell_t smn_PbAddVector(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *vecParams; cell_t *vecParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &vecParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
Vector vec( Vector vec(
sp_ctof(vecParams[0]), sp_ctof(vecParams[0]),
@ -698,11 +657,7 @@ static cell_t smn_PbAddVector2D(IPluginContext *pCtx, const cell_t *params)
GET_FIELD_NAME_OR_ERR(); GET_FIELD_NAME_OR_ERR();
cell_t *vecParams; cell_t *vecParams;
if ((err=pCtx->LocalToPhysAddr(params[3], &vecParams)) != SP_ERROR_NONE) pCtx->LocalToPhysAddr(params[3], &vecParams);
{
pCtx->ThrowNativeErrorEx(err, NULL);
return 0;
}
Vector2D vec( Vector2D vec(
sp_ctof(vecParams[0]), sp_ctof(vecParams[0]),

View File

@ -290,8 +290,7 @@ DataStatus DecodeValveParam(IPluginContext *pContext,
case Valve_Vector: case Valve_Vector:
{ {
cell_t *addr; cell_t *addr;
int err; pContext->LocalToPhysAddr(param, &addr);
err = pContext->LocalToPhysAddr(param, &addr);
unsigned char *mem = (unsigned char *)buffer; unsigned char *mem = (unsigned char *)buffer;
if (data->type == PassType_Basic) 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 /* Use placement new to initialize the object cleanly
* This has no destructor so we don't need to do * This has no destructor so we don't need to do
* DestroyValveParam() or something :] * DestroyValveParam() or something :]

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_
#define _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_
/**
* @file INativeInvoker.h
* @brief Interface for invoking natives.
*/
#include <IShareSys.h>
#include <sp_vm_api.h>
#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_ */

View File

@ -85,6 +85,10 @@ class FixedArray : public AllocPolicy
data_[index] = t; data_[index] = t;
} }
T *buffer() const {
return data_;
}
private: private:
FixedArray(const FixedArray &other) KE_DELETE; FixedArray(const FixedArray &other) KE_DELETE;
FixedArray &operator =(const FixedArray &other) KE_DELETE; FixedArray &operator =(const FixedArray &other) KE_DELETE;

View File

@ -230,13 +230,24 @@ class Label
assert(this->offset() == offset); assert(this->offset() == offset);
} }
private: protected:
// Note that 0 as an invalid offset is okay, because the offset we save for // 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 // pending jumps are after the jump opcode itself, and therefore 0 is never
// valid, since there are no 0-byte jumps. // valid, since there are no 0-byte jumps.
uint32_t status_; 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 // 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 // are within the code buffer, and thus aren't known yet, and will be
// automatically fixed up when calling emitToExecutableMemory(). // automatically fixed up when calling emitToExecutableMemory().

View File

@ -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 // Copyright (C) 2006-2015 AlliedModders LLC
// //
@ -19,15 +19,19 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <assert.h>
#include "sp_vm_types.h" #include "sp_vm_types.h"
/** SourcePawn Engine API Versions */ /** SourcePawn Engine API Versions */
#define SOURCEPAWN_ENGINE2_API_VERSION 9 #define SOURCEPAWN_ENGINE2_API_VERSION 0xA
#define SOURCEPAWN_API_VERSION 0x0209 #define SOURCEPAWN_API_VERSION 0x020A
namespace SourceMod { namespace SourceMod {
struct IdentityToken_t; struct IdentityToken_t;
}; };
namespace sp {
class Environment;
};
struct sp_context_s; struct sp_context_s;
typedef struct sp_context_s sp_context_t; typedef struct sp_context_s sp_context_t;
@ -36,6 +40,8 @@ namespace SourcePawn
{ {
class IVirtualMachine; class IVirtualMachine;
class IPluginRuntime; class IPluginRuntime;
class ISourcePawnEngine2;
class ISourcePawnEnvironment;
/* Parameter flags */ /* Parameter flags */
#define SM_PARAM_COPYBACK (1<<0) /**< Copy an array/reference back after call */ #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 * @brief Executes the function, resets the pushed parameter list, and
* performs any copybacks. * 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. * @param result Pointer to store return value in.
* @return Error code, if any. * @return Error code, if any.
*/ */
virtual int Execute(cell_t *result) =0; virtual int Execute(cell_t *result) =0;
/** /**
* @brief Executes the function with the given parameter array. * @brief This function is deprecated. If invoked, it reports an error.
* 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.
* *
* @param params Array of cell parameters. * @param params Unused.
* @param num_params Number of parameters to push. * @param num_params Unused.
* @param result Pointer to store result of function on return. * @param result Unused.
* @return SourcePawn error code (if any). * @return SP_ERROR_ABORTED.
*/ */
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) =0; 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; virtual funcid_t GetFunctionID() =0;
/** /**
* @brief Executes the forward, resets the pushed parameter list, and * @brief This function is deprecated. If invoked, it reports an error.
* performs any copybacks.
* *
* Note: A function can only be executed given a runtime it was created in. * @param ctx Unused.
* * @param result Unused.
* @param ctx Context to execute the function in. * @return SP_ERROR_ABORTED.
* @param result Pointer to store return value in.
* @return Error code, if any.
*/ */
virtual int Execute2(IPluginContext *ctx, cell_t *result) =0; virtual int Execute2(IPluginContext *ctx, cell_t *result) =0;
/** /**
* @brief Executes the function with the given parameter array. * @brief This function is deprecated. If invoked, it reports an error.
* 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.
* *
* Note: A function can only be executed given a runtime it was created in. * @param ctx Unused.
* * @param params Unused.
* @param ctx Context to execute the function in. * @param num_params Unused.
* @param params Array of cell parameters. * @param result Unused.
* @param num_params Number of parameters to push. * @return SP_ERROR_ABORTED.
* @param result Pointer to store result of function on return.
* @return SourcePawn error code (if any).
*/ */
virtual int CallFunction2(IPluginContext *ctx, virtual int CallFunction2(IPluginContext *ctx,
const cell_t *params, const cell_t *params,
@ -240,6 +240,20 @@ namespace SourcePawn
* @return IPluginRuntime pointer. * @return IPluginRuntime pointer.
*/ */
virtual IPluginRuntime *GetParentRuntime() =0; 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 index Unused.
* @param native 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; 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. * @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; virtual int ApplyCompilationOptions(ICompilation *co) =0;
@ -448,13 +462,13 @@ namespace SourcePawn
*/ */
virtual unsigned char *GetDataHash() =0; virtual unsigned char *GetDataHash() =0;
/** /**
* @brief Update the native binding at the given index. * @brief Update the native binding at the given index.
* *
* @param pfn Native function pointer. * @param pfn Native function pointer.
* @param flags Native flags. * @param flags Native flags.
* @param user User data pointer. * @param user User data pointer.
*/ */
virtual int UpdateNativeBinding(uint32_t index, SPVM_NATIVE_FUNC pfn, uint32_t flags, void *data) = 0; 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. * @return Native pointer, or NULL on failure.
*/ */
virtual const sp_native_t *GetNative(uint32_t index) = 0; 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 index Unused.
* @param native 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; 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. * @brief Throws a error and halts any current execution.
* *
* This function is deprecated. Use ReportError() instead.
*
* @param error The error number to set. * @param error The error number to set.
* @param msg Custom error message format. NULL to use default. * @param msg Custom error message format. NULL to use default.
* @param ... Message format arguments, if any. * @param ... Message format arguments, if any.
@ -744,6 +765,8 @@ namespace SourcePawn
/** /**
* @brief Throws a generic native error and halts any current execution. * @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 msg Custom error message format. NULL to set no message.
* @param ... Message format arguments, if any. * @param ... Message format arguments, if any.
* @return 0 for convenience. * @return 0 for convenience.
@ -816,14 +839,13 @@ namespace SourcePawn
virtual IPluginRuntime *GetRuntime() =0; virtual IPluginRuntime *GetRuntime() =0;
/** /**
* @brief Executes a function in the context. The function must be * @brief This function is deprecated. If invoked, it reports an error.
* a member of the context's parent runtime.
* *
* @param function Function. * @param function Unused.
* @param params Parameters. * @param params Unused.
* @param num_params Number of parameters in the parameter array. * @param num_params Unused.
* @param result Optional pointer to store the result on success. * @param result Unused.
* @return Error code. * @return SP_ERROR_ABORTED.
*/ */
virtual int Execute2(IPluginFunction *function, virtual int Execute2(IPluginFunction *function,
const cell_t *params, const cell_t *params,
@ -832,9 +854,12 @@ namespace SourcePawn
/** /**
* @brief Returns whether a context is in an error state. * @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 * 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; virtual int GetLastNativeError() =0;
@ -870,81 +895,171 @@ namespace SourcePawn
virtual bool GetKey(int k, void **value) =0; 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; 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. * @brief Report an error.
*/ *
struct CallStackInfo * @param message Error message format.
{ * @param ... Formatting arguments.
const char *filename; /**< NULL if not found */ */
unsigned int line; /**< 0 if not found */ virtual void ReportError(const char *fmt, ...) = 0;
const char *function; /**< NULL if not found */
/**
* @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: 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; virtual IPluginContext *Context() const = 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;
}; };
/**
* @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. * @brief Provides callbacks for debug information.
@ -953,12 +1068,13 @@ namespace SourcePawn
{ {
public: public:
/** /**
* @brief Invoked on a context execution error. * @brief No longer invoked.
* *
* @param ctx Context. * @param ctx Unused.
* @param error Object holding error information and a backtrace. * @param error Unused.
*/ */
virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) =0; virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error)
{}
/** /**
* @brief Called on debug spew. * @brief Called on debug spew.
@ -967,6 +1083,15 @@ namespace SourcePawn
* @param fmt Message formatting arguments (printf-style). * @param fmt Message formatting arguments (printf-style).
*/ */
virtual void OnDebugSpew(const char *msg, ...) =0; 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. * @brief Return the name of the profiling tool.
* *
* @return Profiling tool name. * @return Profiling tool name.
*/ */
virtual const char *Name() = 0; virtual const char *Name() = 0;
/** /**
* @brief Description of the profiler. * @brief Description of the profiler.
* *
* @return Description. * @return Description.
*/ */
virtual const char *Description() = 0; virtual const char *Description() = 0;
/** /**
* @brief Called to render help text. * @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; virtual void RenderHelp(void (*render)(const char *fmt, ...)) = 0;
@ -1013,7 +1138,7 @@ namespace SourcePawn
/** /**
* @brief Initiate a stop command. * @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; 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. * @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; virtual bool IsActive() = 0;
/** /**
* @brief Returns whether the profiler is attached. * @brief Returns whether the profiler is attached.
* *
* @return True if attached, false otherwise. * @return True if attached, false otherwise.
*/ */
virtual bool IsAttached() = 0; virtual bool IsAttached() = 0;
@ -1045,8 +1170,8 @@ namespace SourcePawn
* *
* LeaveScope() mus be called exactly once for each call to EnterScope(). * LeaveScope() mus be called exactly once for each call to EnterScope().
* *
* @param group A named budget group, or NULL for the default. * @param group A named budget group, or NULL for the default.
* @param name Event name. * @param name Event name.
*/ */
virtual void EnterScope(const char *group, const char *name) = 0; virtual void EnterScope(const char *group, const char *name) = 0;
@ -1114,10 +1239,10 @@ namespace SourcePawn
virtual void ExecFree(void *address) =0; virtual void ExecFree(void *address) =0;
/** /**
* @brief Sets the debug listener. This should only be called once. * @brief Sets the debug listener.
* If called successively (using manual chaining), only the last function should *
* attempt to call back into the same plugin. Otherwise, globally cached states * This should be called once on application startup. It is
* can be accidentally overwritten. * not considered part of the userland API and may change at any time.
* *
* @param listener Pointer to an IDebugListener. * @param listener Pointer to an IDebugListener.
* @return Old IDebugListener, or NULL if none. * @return Old IDebugListener, or NULL if none.
@ -1166,6 +1291,8 @@ namespace SourcePawn
virtual void FreePageMemory(void *ptr) =0; virtual void FreePageMemory(void *ptr) =0;
}; };
class ExceptionHandler;
/** /**
* @brief Outlines the interface a Virtual Machine (JIT) must expose * @brief Outlines the interface a Virtual Machine (JIT) must expose
*/ */
@ -1228,10 +1355,10 @@ namespace SourcePawn
virtual void DestroyFakeNative(SPVM_NATIVE_FUNC func) =0; virtual void DestroyFakeNative(SPVM_NATIVE_FUNC func) =0;
/** /**
* @brief Sets the debug listener. This should only be called once. * @brief Sets the debug listener.
* If called successively (using manual chaining), only the last function should *
* attempt to call back into the same plugin. Otherwise, globally cached states * This should be called once on application startup. It is
* can be accidentally overwritten. * not considered part of the userland API and may change at any time.
* *
* @param listener Pointer to an IDebugListener. * @param listener Pointer to an IDebugListener.
* @return Old IDebugListener, or NULL if none. * @return Old IDebugListener, or NULL if none.
@ -1248,6 +1375,9 @@ namespace SourcePawn
/** /**
* @brief Returns the string representation of an error message. * @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. * @param err Error code.
* @return Error string, or NULL if not found. * @return Error string, or NULL if not found.
*/ */
@ -1326,6 +1456,11 @@ namespace SourcePawn
* @return New runtime pointer, or NULL on failure. * @return New runtime pointer, or NULL on failure.
*/ */
virtual IPluginRuntime *LoadBinaryFromFile(const char *file, char *error, size_t maxlength) = 0; 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 // @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 // all plugin memory. This should not be called while plugins have
// active code running on the stack. // active code running on the stack.
virtual void Shutdown() = 0; 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. // @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 // @brief A function named "GetSourcePawnFactory" is exported from the
// SourcePawn DLL, conforming to the following signature: // SourcePawn DLL, conforming to the following signature:
typedef ISourcePawnFactory *(*GetSourcePawnFactoryFn)(int apiVersion); 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_ #endif //_INCLUDE_SOURCEPAWN_VM_API_H_

View File

@ -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_OUT_OF_MEMORY 28 /**< Out of memory */
#define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */ #define SP_ERROR_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
#define SP_ERROR_TIMEOUT 30 /**< Timeout */ #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! */ //Hey you! Update the string table if you add to the end of me! */
/********************************************** /**********************************************

View File

@ -36,13 +36,13 @@ library.sources += [
'code-allocator.cpp', 'code-allocator.cpp',
'code-stubs.cpp', 'code-stubs.cpp',
'compiled-function.cpp', 'compiled-function.cpp',
'debug-trace.cpp',
'environment.cpp', 'environment.cpp',
'file-utils.cpp', 'file-utils.cpp',
'opcodes.cpp', 'opcodes.cpp',
'plugin-context.cpp', 'plugin-context.cpp',
'plugin-runtime.cpp', 'plugin-runtime.cpp',
'scripted-invoker.cpp', 'scripted-invoker.cpp',
'stack-frames.cpp',
'smx-v1-image.cpp', 'smx-v1-image.cpp',
'watchdog_timer.cpp', 'watchdog_timer.cpp',
'x86/code-stubs-x86.cpp', 'x86/code-stubs-x86.cpp',

View File

@ -178,14 +178,10 @@ SourcePawnEngine2::SourcePawnEngine2()
{ {
} }
static size_t size_t
UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) 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); size_t len = vsnprintf(buffer, maxlength, fmt, ap);
va_end(ap);
if (len >= maxlength) { if (len >= maxlength) {
buffer[maxlength - 1] = '\0'; buffer[maxlength - 1] = '\0';
@ -194,6 +190,18 @@ UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
return len; 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 * IPluginRuntime *
SourcePawnEngine2::LoadPlugin(ICompilation *co, const char *file, int *err) SourcePawnEngine2::LoadPlugin(ICompilation *co, const char *file, int *err)
{ {
@ -256,13 +264,13 @@ SourcePawnEngine2::LoadBinaryFromFile(const char *file, char *error, size_t maxl
# endif # endif
) )
{ {
pRuntime->SetName(&file[i + 1]); pRuntime->SetNames(file, &file[i + 1]);
break; break;
} }
} }
if (!pRuntime->Name()) if (!pRuntime->Name())
pRuntime->SetName(file); pRuntime->SetNames(file, file);
return pRuntime; return pRuntime;
} }
@ -341,7 +349,9 @@ SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t memory)
return NULL; return NULL;
} }
rt->SetName(name != NULL ? name : "<anonymous>"); if (!name)
name = "<anonymous>";
rt->SetNames(name, name);
return rt; return rt;
} }
@ -387,3 +397,9 @@ SourcePawnEngine2::SetProfilingTool(IProfilingTool *tool)
{ {
Environment::get()->SetProfiler(tool); Environment::get()->SetProfiler(tool);
} }
ISourcePawnEnvironment *
SourcePawnEngine2::Environment()
{
return Environment::get();
}

View File

@ -68,8 +68,12 @@ class SourcePawnEngine2 : public ISourcePawnEngine2
void DisableProfiling() KE_OVERRIDE; void DisableProfiling() KE_OVERRIDE;
void SetProfilingTool(IProfilingTool *tool) KE_OVERRIDE; void SetProfilingTool(IProfilingTool *tool) KE_OVERRIDE;
IPluginRuntime *LoadBinaryFromFile(const char *file, char *error, size_t maxlength) 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_ #endif // _include_sourcepawn_vm_api_h_

View File

@ -18,8 +18,7 @@ using namespace sp;
CodeStubs::CodeStubs(Environment *env) CodeStubs::CodeStubs(Environment *env)
: env_(env), : env_(env),
invoke_stub_(nullptr), invoke_stub_(nullptr),
return_stub_(nullptr), return_stub_(nullptr)
timeout_stub_(nullptr)
{ {
} }

View File

@ -40,9 +40,6 @@ class CodeStubs
void *ReturnStub() const { void *ReturnStub() const {
return return_stub_; return return_stub_;
} }
void *TimeoutStub() const {
return return_stub_;
}
private: private:
bool InitializeFeatureDetection(); bool InitializeFeatureDetection();
@ -52,7 +49,6 @@ class CodeStubs
Environment *env_; Environment *env_;
void *invoke_stub_; void *invoke_stub_;
void *return_stub_; // Owned by invoke_stub_. void *return_stub_; // Owned by invoke_stub_.
void *timeout_stub_; // Owned by invoke_stub_.
}; };
} }

View File

@ -16,10 +16,15 @@
using namespace sp; using namespace sp;
CompiledFunction::CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray<LoopEdge> *edges) CompiledFunction::CompiledFunction(void *entry_addr, size_t code_length,
cell_t pcode_offs,
FixedArray<LoopEdge> *edges,
FixedArray<CipMapEntry> *cipmap)
: entry_(entry_addr), : entry_(entry_addr),
code_length_(code_length),
code_offset_(pcode_offs), code_offset_(pcode_offs),
edges_(edges) edges_(edges),
cip_map_(cipmap)
{ {
} }
@ -27,3 +32,40 @@ CompiledFunction::~CompiledFunction()
{ {
Environment::get()->FreeCode(entry_); 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<const CipMapEntry *>(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<CipMapEntry *>(ptr)->cipoffs;
}

View File

@ -23,14 +23,31 @@ using namespace ke;
struct LoopEdge struct LoopEdge
{ {
// Offset to the patchable jump instruction, such that (base + offset - 4)
// yields a patchable location.
uint32_t offset; uint32_t offset;
// The displacement to either the timeout routine or the original
// displacement, depending on the timeout state.
int32_t disp32; 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 class CompiledFunction
{ {
public: public:
CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray<LoopEdge> *edges); CompiledFunction(void *entry_addr,
size_t code_length,
cell_t pcode_offs,
FixedArray<LoopEdge> *edges,
FixedArray<CipMapEntry> *cip_map);
~CompiledFunction(); ~CompiledFunction();
public: public:
@ -43,14 +60,18 @@ class CompiledFunction
uint32_t NumLoopEdges() const { uint32_t NumLoopEdges() const {
return edges_->length(); return edges_->length();
} }
const LoopEdge &GetLoopEdge(size_t i) const { LoopEdge &GetLoopEdge(size_t i) {
return edges_->at(i); return edges_->at(i);
} }
ucell_t FindCipByPc(void *pc);
private: private:
void *entry_; void *entry_;
size_t code_length_;
cell_t code_offset_; cell_t code_offset_;
AutoPtr<FixedArray<LoopEdge>> edges_; AutoPtr<FixedArray<LoopEdge>> edges_;
AutoPtr<FixedArray<CipMapEntry>> cip_map_;
}; };
} }

View File

@ -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;
}

View File

@ -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 <sp_vm_api.h>
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_

View File

@ -35,6 +35,7 @@
#include <am-utility.h> // Replace with am-cxx later. #include <am-utility.h> // Replace with am-cxx later.
#include "dll_exports.h" #include "dll_exports.h"
#include "environment.h" #include "environment.h"
#include "stack-frames.h"
using namespace ke; using namespace ke;
using namespace sp; using namespace sp;
@ -55,72 +56,36 @@ public:
} sFactory; } sFactory;
#ifdef SPSHELL #ifdef SPSHELL
template <typename T> 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; 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] <unknown>\n", index);
continue;
}
if (iter.IsScriptedFrame()) {
const char *file = iter.FilePath();
if (!file)
file = "<unknown>";
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 class ShellDebugListener : public IDebugListener
{ {
public: public:
void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) { void ReportError(const IErrorReport &report, IFrameIterator &iter) KE_OVERRIDE {
int n_err = error->GetErrorCode(); fprintf(stdout, "Exception thrown: %s\n", report.Message());
DumpStack(iter);
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 OnDebugSpew(const char *msg, ...) { 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])); 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) static int Execute(const char *file)
{ {
ICompilation *co = sEnv->APIv2()->StartCompilation(); char error[255];
if (!co) { AutoPtr<IPluginRuntime> rt(sEnv->APIv2()->LoadBinaryFromFile(file, error, sizeof(error)));
fprintf(stderr, "Could not create a compilation context\n");
return 1;
}
int err;
AutoT<IPluginRuntime> rt(sEnv->APIv2()->LoadPlugin(co, file, &err));
if (!rt) { if (!rt) {
fprintf(stderr, "Could not load plugin: %s\n", sEnv->GetErrorString(err)); fprintf(stderr, "Could not load plugin: %s\n", error);
return 1; return 1;
} }
@ -201,6 +191,9 @@ static int Execute(const char *file)
BindNative(rt, "printnums", PrintNums); BindNative(rt, "printnums", PrintNums);
BindNative(rt, "printfloat", PrintFloat); BindNative(rt, "printfloat", PrintFloat);
BindNative(rt, "donothing", DoNothing); BindNative(rt, "donothing", DoNothing);
BindNative(rt, "execute", DoExecute);
BindNative(rt, "invoke", DoInvoke);
BindNative(rt, "dump_stack_trace", DumpStackTrace);
IPluginFunction *fun = rt->GetFunctionByName("main"); IPluginFunction *fun = rt->GetFunctionByName("main");
if (!fun) if (!fun)
@ -208,10 +201,13 @@ static int Execute(const char *file)
IPluginContext *cx = rt->GetDefaultContext(); IPluginContext *cx = rt->GetDefaultContext();
int result = fun->Execute2(cx, &err); int result;
if (err != SP_ERROR_NONE) { {
fprintf(stderr, "Error executing main(): %s\n", sEnv->GetErrorString(err)); ExceptionHandler eh(cx);
return 1; if (!fun->Invoke(&result)) {
fprintf(stderr, "Error executing main: %s\n", eh.Message());
return 1;
}
} }
return result; return result;

View File

@ -13,7 +13,6 @@
#include "environment.h" #include "environment.h"
#include "x86/jit_x86.h" #include "x86/jit_x86.h"
#include "watchdog_timer.h" #include "watchdog_timer.h"
#include "debug-trace.h"
#include "api.h" #include "api.h"
#include "code-stubs.h" #include "code-stubs.h"
#include "watchdog_timer.h" #include "watchdog_timer.h"
@ -25,10 +24,12 @@ static Environment *sEnvironment = nullptr;
Environment::Environment() Environment::Environment()
: debugger_(nullptr), : debugger_(nullptr),
exception_code_(SP_ERROR_NONE),
profiler_(nullptr), profiler_(nullptr),
jit_enabled_(true), jit_enabled_(true),
profiling_enabled_(false), profiling_enabled_(false),
code_pool_(nullptr) code_pool_(nullptr),
top_(nullptr)
{ {
} }
@ -150,7 +151,9 @@ static const char *sErrorMsgTable[] =
"Plugin format is too new", "Plugin format is too new",
"Out of memory", "Out of memory",
"Integer overflow", "Integer overflow",
"Script execution timed out" "Script execution timed out",
"Custom error",
"Fatal error"
}; };
const char * const char *
@ -161,17 +164,6 @@ Environment::GetErrorString(int error)
return sErrorMsgTable[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 * void *
Environment::AllocateCode(size_t size) Environment::AllocateCode(size_t size)
{ {
@ -198,6 +190,15 @@ Environment::DeregisterRuntime(PluginRuntime *rt)
runtimes_.remove(rt); runtimes_.remove(rt);
} }
static inline void
SwapLoopEdge(uint8_t *code, LoopEdge &e)
{
int32_t *loc = reinterpret_cast<int32_t *>(code + e.offset - 4);
int32_t new_disp32 = e.disp32;
e.disp32 = *loc;
*loc = new_disp32;
}
void void
Environment::PatchAllJumpsForTimeout() Environment::PatchAllJumpsForTimeout()
{ {
@ -208,11 +209,8 @@ Environment::PatchAllJumpsForTimeout()
CompiledFunction *fun = rt->GetJitFunction(i); CompiledFunction *fun = rt->GetJitFunction(i);
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress()); uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
for (size_t j = 0; j < fun->NumLoopEdges(); j++) { for (size_t j = 0; j < fun->NumLoopEdges(); j++)
const LoopEdge &e = fun->GetLoopEdge(j); SwapLoopEdge(base, fun->GetLoopEdge(j));
int32_t diff = intptr_t(code_stubs_->TimeoutStub()) - intptr_t(base + e.offset);
*reinterpret_cast<int32_t *>(base + e.offset - 4) = diff;
}
} }
} }
} }
@ -227,10 +225,8 @@ Environment::UnpatchAllJumpsFromTimeout()
CompiledFunction *fun = rt->GetJitFunction(i); CompiledFunction *fun = rt->GetJitFunction(i);
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress()); uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
for (size_t j = 0; j < fun->NumLoopEdges(); j++) { for (size_t j = 0; j < fun->NumLoopEdges(); j++)
const LoopEdge &e = fun->GetLoopEdge(j); SwapLoopEdge(base, fun->GetLoopEdge(j));
*reinterpret_cast<int32_t *>(base + e.offset - 4) = e.disp32;
}
} }
} }
} }
@ -238,16 +234,168 @@ Environment::UnpatchAllJumpsFromTimeout()
int int
Environment::Invoke(PluginRuntime *runtime, CompiledFunction *fn, cell_t *result) 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(); PluginContext *cx = runtime->GetBaseContext();
// Note that cip, hp, sp are saved and restored by Execute2().
*cx->addressOfCip() = fn->GetCodeOffset();
InvokeStubFn invoke = code_stubs_->InvokeStub(); InvokeStubFn invoke = code_stubs_->InvokeStub();
return invoke(cx, fn->GetEntryAddress(), result);
EnterInvoke(); }
int err = invoke(cx, fn->GetEntryAddress(), result);
LeaveInvoke(); void
Environment::ReportError(int code)
return err; {
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_;
} }

View File

@ -19,6 +19,7 @@
#include <am-thread-utils.h> #include <am-thread-utils.h>
#include "code-allocator.h" #include "code-allocator.h"
#include "plugin-runtime.h" #include "plugin-runtime.h"
#include "stack-frames.h"
namespace sp { namespace sp {
@ -54,9 +55,18 @@ class Environment : public ISourcePawnEnvironment
bool InstallWatchdogTimer(int timeout_ms); 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. // Runtime functions.
const char *GetErrorString(int err); 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. // Allocate and free executable memory.
void *AllocateCode(size_t size); void *AllocateCode(size_t size);
@ -104,19 +114,40 @@ class Environment : public ISourcePawnEnvironment
return watchdog_timer_; return watchdog_timer_;
} }
bool hasPendingException() const;
void clearPendingException();
int getPendingExceptionCode() const;
// These are indicators used for the watchdog timer. // These are indicators used for the watchdog timer.
uintptr_t FrameId() const { uintptr_t FrameId() const {
return frame_id_; return frame_id_;
} }
bool RunningCode() const { bool RunningCode() const {
return invoke_depth_ != 0; return !!top_;
} }
void EnterInvoke() { void enterInvoke(InvokeFrame *frame) {
if (invoke_depth_++ == 0) if (!top_)
frame_id_++; frame_id_++;
top_ = frame;
} }
void LeaveInvoke() { void leaveInvoke() {
invoke_depth_--; 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: private:
@ -129,6 +160,10 @@ class Environment : public ISourcePawnEnvironment
ke::Mutex mutex_; ke::Mutex mutex_;
IDebugListener *debugger_; IDebugListener *debugger_;
ExceptionHandler *eh_top_;
int exception_code_;
char exception_message_[1024];
IProfilingTool *profiler_; IProfilingTool *profiler_;
bool jit_enabled_; bool jit_enabled_;
bool profiling_enabled_; bool profiling_enabled_;
@ -137,9 +172,11 @@ class Environment : public ISourcePawnEnvironment
ke::InlineList<PluginRuntime> runtimes_; ke::InlineList<PluginRuntime> runtimes_;
uintptr_t frame_id_; uintptr_t frame_id_;
uintptr_t invoke_depth_;
ke::AutoPtr<CodeStubs> code_stubs_; ke::AutoPtr<CodeStubs> code_stubs_;
InvokeFrame *top_;
ExitFrame exit_frame_;
}; };
class EnterProfileScope class EnterProfileScope

View File

@ -30,14 +30,13 @@ using namespace SourcePawn;
static const size_t kMinHeapSize = 16384; static const size_t kMinHeapSize = 16384;
PluginContext::PluginContext(PluginRuntime *pRuntime) PluginContext::PluginContext(PluginRuntime *pRuntime)
: m_pRuntime(pRuntime), : env_(Environment::get()),
m_pRuntime(pRuntime),
memory_(nullptr), memory_(nullptr),
data_size_(m_pRuntime->data().length), data_size_(m_pRuntime->data().length),
mem_size_(m_pRuntime->image()->HeapSize()), mem_size_(m_pRuntime->image()->HeapSize()),
m_pNullVec(nullptr), m_pNullVec(nullptr),
m_pNullString(nullptr), m_pNullString(nullptr)
m_CustomMsg(false),
m_InExec(false)
{ {
// Compute and align a minimum memory amount. // Compute and align a minimum memory amount.
if (mem_size_ < data_size_) if (mem_size_ < data_size_)
@ -52,9 +51,6 @@ PluginContext::PluginContext(PluginRuntime *pRuntime)
hp_ = data_size_; hp_ = data_size_;
sp_ = mem_size_ - sizeof(cell_t); sp_ = mem_size_ - sizeof(cell_t);
frm_ = sp_; frm_ = sp_;
rp_ = 0;
last_native_ = -1;
native_error_ = SP_ERROR_NONE;
tracker_.pBase = (ucell_t *)malloc(1024); tracker_.pBase = (ucell_t *)malloc(1024);
tracker_.pCur = tracker_.pBase; tracker_.pCur = tracker_.pBase;
@ -133,56 +129,23 @@ PluginContext::Execute(uint32_t code_addr, cell_t *result)
return SP_ERROR_ABORTED; 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 cell_t
PluginContext::ThrowNativeErrorEx(int error, const char *msg, ...) PluginContext::ThrowNativeErrorEx(int error, const char *msg, ...)
{ {
if (!m_InExec) va_list ap;
return 0; va_start(ap, msg);
env_->ReportErrorVA(error, msg, ap);
native_error_ = error; va_end(ap);
if (msg) {
va_list ap;
va_start(ap, msg);
SetErrorMessage(msg, ap);
va_end(ap);
}
return 0; return 0;
} }
cell_t cell_t
PluginContext::ThrowNativeError(const char *msg, ...) PluginContext::ThrowNativeError(const char *msg, ...)
{ {
if (!m_InExec) va_list ap;
return 0; va_start(ap, msg);
env_->ReportErrorVA(SP_ERROR_NATIVE, msg, ap);
native_error_ = SP_ERROR_NATIVE; va_end(ap);
if (msg) {
va_list ap;
va_start(ap, msg);
SetErrorMessage(msg, ap);
va_end(ap);
}
return 0; return 0;
} }
@ -540,133 +503,124 @@ PluginContext::GetNullRef(SP_NULL_TYPE type)
bool bool
PluginContext::IsInExec() PluginContext::IsInExec()
{ {
return m_InExec; for (InvokeFrame *ivk = env_->top(); ivk; ivk = ivk->prev()) {
if (ivk->cx() == this)
return true;
}
return false;
} }
int int
PluginContext::Execute2(IPluginFunction *function, const cell_t *params, unsigned int num_params, cell_t *result) PluginContext::Execute2(IPluginFunction *function, const cell_t *params, unsigned int num_params, cell_t *result)
{ {
int ir; ReportErrorNumber(SP_ERROR_ABORTED);
int serial; return SP_ERROR_ABORTED;
cell_t *sp; }
CompiledFunction *fn;
cell_t _ignore_result;
bool
PluginContext::Invoke(funcid_t fnid, const cell_t *params, unsigned int num_params, cell_t *result)
{
EnterProfileScope profileScope("SourcePawn", "EnterJIT"); EnterProfileScope profileScope("SourcePawn", "EnterJIT");
if (!Environment::get()->watchdog()->HandleInterrupt()) if (!env_->watchdog()->HandleInterrupt()) {
return SP_ERROR_TIMEOUT; ReportErrorNumber(SP_ERROR_TIMEOUT);
return false;
}
funcid_t fnid = function->GetFunctionID(); assert((fnid & 1) != 0);
if (!(fnid & 1))
return SP_ERROR_INVALID_ADDRESS;
unsigned public_id = fnid >> 1; unsigned public_id = fnid >> 1;
ScriptedInvoker *cfun = m_pRuntime->GetPublicFunction(public_id); ScriptedInvoker *cfun = m_pRuntime->GetPublicFunction(public_id);
if (!cfun) if (!cfun) {
return SP_ERROR_NOT_FOUND; ReportErrorNumber(SP_ERROR_NOT_FOUND);
return false;
}
if (m_pRuntime->IsPaused()) if (m_pRuntime->IsPaused()) {
return SP_ERROR_NOT_RUNNABLE; ReportErrorNumber(SP_ERROR_NOT_RUNNABLE);
return false;
}
if ((cell_t)(hp_ + 16*sizeof(cell_t)) > (cell_t)(sp_ - (sizeof(cell_t) * (num_params + 1)))) if ((cell_t)(hp_ + 16*sizeof(cell_t)) > (cell_t)(sp_ - (sizeof(cell_t) * (num_params + 1)))) {
return SP_ERROR_STACKLOW; 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) if (result == NULL)
result = &_ignore_result; result = &ignore_result;
/* We got this far. It's time to start profiling. */ /* We got this far. It's time to start profiling. */
EnterProfileScope scriptScope("SourcePawn", cfun->FullName()); EnterProfileScope scriptScope("SourcePawn", cfun->FullName());
/* See if we have to compile the callee. */ /* 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. */ /* We might not have to - check pcode offset. */
if ((fn = cfun->cachedCompiledFunction()) == nullptr) { if ((fn = cfun->cachedCompiledFunction()) == nullptr) {
fn = m_pRuntime->GetJittedFunctionByOffset(cfun->Public()->code_offs); fn = m_pRuntime->GetJittedFunctionByOffset(cfun->Public()->code_offs);
if (!fn) { if (!fn) {
if ((fn = CompileFunction(m_pRuntime, cfun->Public()->code_offs, &ir)) == NULL) int err = SP_ERROR_NONE;
return ir; if ((fn = CompileFunction(m_pRuntime, cfun->Public()->code_offs, &err)) == NULL) {
ReportErrorNumber(err);
return false;
}
} }
cfun->setCachedCompiledFunction(fn); cfun->setCachedCompiledFunction(fn);
} }
} else {
ReportError("JIT is not enabled!");
return false;
} }
/* Save our previous state. */ /* Save our previous state. */
cell_t save_sp = sp_;
bool save_exec; cell_t save_hp = hp_;
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_;
/* Push parameters */ /* Push parameters */
sp_ -= sizeof(cell_t) * (num_params + 1); sp_ -= sizeof(cell_t) * (num_params + 1);
sp = (cell_t *)(memory_ + sp_); cell_t *sp = (cell_t *)(memory_ + sp_);
sp[0] = num_params; sp[0] = num_params;
for (unsigned int i = 0; i < num_params; i++) for (unsigned int i = 0; i < num_params; i++)
sp[i + 1] = 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. // Enter the execution engine.
Environment *env = Environment::get(); int ir;
ir = env->Invoke(m_pRuntime, fn, result); {
InvokeFrame ivkframe(this, fn->GetCodeOffset());
/* Restore some states, stop the frame tracer */ Environment *env = env_;
ir = env->Invoke(m_pRuntime, fn, result);
m_InExec = save_exec; }
if (ir == SP_ERROR_NONE) { if (ir == SP_ERROR_NONE) {
native_error_ = SP_ERROR_NONE; // Verify that our state is still sane.
if (sp_ != save_sp) { if (sp_ != save_sp) {
ir = SP_ERROR_STACKLEAK; env_->ReportErrorFmt(
_SetErrorMessage("Stack leak detected: sp:%d should be %d!", SP_ERROR_STACKLEAK,
"Stack leak detected: sp:%d should be %d!",
sp_, sp_,
save_sp); save_sp);
return false;
} }
if (hp_ != save_hp) { if (hp_ != save_hp) {
ir = SP_ERROR_HEAPLEAK; env_->ReportErrorFmt(
_SetErrorMessage("Heap leak detected: hp:%d should be %d!", SP_ERROR_HEAPLEAK,
"Heap leak detected: hp:%d should be %d!",
hp_, hp_,
save_hp); save_hp);
} return false;
if (rp_ != save_rp) {
ir = SP_ERROR_STACKLEAK;
_SetErrorMessage("Return stack leak detected: rp:%d should be %d!",
rp_,
save_rp);
} }
} }
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; sp_ = save_sp;
hp_ = save_hp; hp_ = save_hp;
rp_ = save_rp; return ir == SP_ERROR_NONE;
cip_ = save_cip;
last_native_ = save_n_idx;
native_error_ = SP_ERROR_NONE;
m_MsgCache[0] = '\0';
m_CustomMsg = false;
return ir;
} }
IPluginRuntime * IPluginRuntime *
@ -678,7 +632,10 @@ PluginContext::GetRuntime()
int int
PluginContext::GetLastNativeError() PluginContext::GetLastNativeError()
{ {
return native_error_; Environment *env = env_;
if (!env->hasPendingException())
return SP_ERROR_NONE;
return env->getPendingExceptionCode();
} }
cell_t * cell_t *
@ -710,7 +667,8 @@ PluginContext::GetKey(int k, void **value)
void void
PluginContext::ClearLastNativeError() PluginContext::ClearLastNativeError()
{ {
native_error_ = SP_ERROR_NONE; if (env_->hasPendingException())
env_->clearPendingException();
} }
int int
@ -757,28 +715,24 @@ PluginContext::invokeNative(ucell_t native_idx, cell_t *params)
cell_t save_sp = sp_; cell_t save_sp = sp_;
cell_t save_hp = hp_; 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); const sp_native_t *native = m_pRuntime->GetNative(native_idx);
if (native->status == SP_NATIVE_UNBOUND) { if (native->status == SP_NATIVE_UNBOUND) {
native_error_ = SP_ERROR_INVALID_NATIVE; ReportErrorNumber(SP_ERROR_INVALID_NATIVE);
return 0; return 0;
} }
cell_t result = native->pfn(this, params); cell_t result = native->pfn(this, params);
if (native_error_ != SP_ERROR_NONE)
return result;
if (save_sp != sp_) { if (save_sp != sp_) {
native_error_ = SP_ERROR_STACKLEAK; if (!env_->hasPendingException())
return result; ReportErrorNumber(SP_ERROR_STACKLEAK);
return 0;
} }
if (save_hp != hp_) { if (save_hp != hp_) {
native_error_ = SP_ERROR_HEAPLEAK; if (!env_->hasPendingException())
return result; ReportErrorNumber(SP_ERROR_HEAPLEAK);
return 0;
} }
return result; return result;
@ -792,15 +746,14 @@ PluginContext::invokeBoundNative(SPVM_NATIVE_FUNC pfn, cell_t *params)
cell_t result = pfn(this, params); cell_t result = pfn(this, params);
if (native_error_ != SP_ERROR_NONE)
return result;
if (save_sp != sp_) { if (save_sp != sp_) {
native_error_ = SP_ERROR_STACKLEAK; if (!env_->hasPendingException())
ReportErrorNumber(SP_ERROR_STACKLEAK);
return result; return result;
} }
if (save_hp != hp_) { if (save_hp != hp_) {
native_error_ = SP_ERROR_HEAPLEAK; if (!env_->hasPendingException())
ReportErrorNumber(SP_ERROR_HEAPLEAK);
return result; return result;
} }
@ -957,3 +910,44 @@ PluginContext::generateArray(cell_t dims, cell_t *stk, bool autozero)
return SP_ERROR_NONE; 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);
}

View File

@ -33,6 +33,9 @@ struct HeapTracker
static const size_t SP_MAX_RETURN_STACK = 1024; static const size_t SP_MAX_RETURN_STACK = 1024;
class Environment;
class PluginContext;
class PluginContext : public IPluginContext class PluginContext : public IPluginContext
{ {
public: public:
@ -88,6 +91,14 @@ class PluginContext : public IPluginContext
bool GetKey(int k, void **value); bool GetKey(int k, void **value);
void Refresh(); void Refresh();
void ClearLastNativeError(); 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 { size_t HeapSize() const {
return mem_size_; return mem_size_;
@ -98,25 +109,16 @@ class PluginContext : public IPluginContext
size_t DataSize() const { size_t DataSize() const {
return data_size_; return data_size_;
} }
PluginRuntime *runtime() const {
return m_pRuntime;
}
public: public:
bool IsInExec(); 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() { static inline size_t offsetOfTracker() {
return offsetof(PluginContext, tracker_); 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() { static inline size_t offsetOfSp() {
return offsetof(PluginContext, sp_); return offsetof(PluginContext, sp_);
} }
@ -127,9 +129,6 @@ class PluginContext : public IPluginContext
return offsetof(PluginContext, memory_); return offsetof(PluginContext, memory_);
} }
int32_t *addressOfCip() {
return &cip_;
}
int32_t *addressOfSp() { int32_t *addressOfSp() {
return &sp_; return &sp_;
} }
@ -140,9 +139,6 @@ class PluginContext : public IPluginContext
return &hp_; return &hp_;
} }
int32_t cip() const {
return cip_;
}
cell_t frm() const { cell_t frm() const {
return frm_; return frm_;
} }
@ -150,25 +146,6 @@ class PluginContext : public IPluginContext
return hp_; 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 popTrackerAndSetHeap();
int pushTracker(uint32_t amount); int pushTracker(uint32_t amount);
@ -176,9 +153,6 @@ class PluginContext : public IPluginContext
int generateFullArray(uint32_t argc, cell_t *argv, int autozero); int generateFullArray(uint32_t argc, cell_t *argv, int autozero);
cell_t invokeNative(ucell_t native_idx, cell_t *params); cell_t invokeNative(ucell_t native_idx, cell_t *params);
cell_t invokeBoundNative(SPVM_NATIVE_FUNC pfn, 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) { inline bool checkAddress(cell_t *stk, cell_t addr) {
if (uint32_t(addr) >= mem_size_) if (uint32_t(addr) >= mem_size_)
@ -194,10 +168,7 @@ class PluginContext : public IPluginContext
} }
private: private:
void SetErrorMessage(const char *msg, va_list ap); Environment *env_;
void _SetErrorMessage(const char *msg, ...);
private:
PluginRuntime *m_pRuntime; PluginRuntime *m_pRuntime;
uint8_t *memory_; uint8_t *memory_;
uint32_t data_size_; uint32_t data_size_;
@ -205,26 +176,12 @@ class PluginContext : public IPluginContext
cell_t *m_pNullVec; cell_t *m_pNullVec;
cell_t *m_pNullString; cell_t *m_pNullString;
char m_MsgCache[1024];
bool m_CustomMsg;
bool m_InExec;
void *m_keys[4]; void *m_keys[4];
bool m_keys_set[4]; bool m_keys_set[4];
// Tracker for local HEA growth. // Tracker for local HEA growth.
HeapTracker tracker_; 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. // Stack, heap, and frame pointer.
cell_t sp_; cell_t sp_;
cell_t hp_; cell_t hp_;

View File

@ -161,11 +161,10 @@ PluginRuntime::GetNativeReplacement(size_t index)
} }
void void
PluginRuntime::SetName(const char *name) PluginRuntime::SetNames(const char *fullname, const char *name)
{ {
size_t len = strlen(name); name_ = name;
name_ = new char[len + 1]; full_name_ = name;
strcpy(name_, name);
} }
static cell_t static cell_t
@ -180,19 +179,21 @@ PluginRuntime::AddJittedFunction(CompiledFunction *fn)
m_JitFunctions.append(fn); m_JitFunctions.append(fn);
ucell_t pcode_offset = fn->GetCodeOffset(); 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 * CompiledFunction *
PluginRuntime::GetJittedFunctionByOffset(cell_t pcode_offset) PluginRuntime::GetJittedFunctionByOffset(cell_t pcode_offset)
{ {
FunctionMap::Result r = function_map_.find(pcode_offset); FunctionMap::Result r = function_map_.find(pcode_offset);
if (r.found()) if (!r.found())
return r->value; return nullptr;
return nullptr; return r->value;
} }
int int

View File

@ -15,6 +15,7 @@
#include <sp_vm_api.h> #include <sp_vm_api.h>
#include <am-vector.h> #include <am-vector.h>
#include <am-string.h>
#include <am-inlinelist.h> #include <am-inlinelist.h>
#include <am-hashmap.h> #include <am-hashmap.h>
#include "compiled-function.h" #include "compiled-function.h"
@ -71,7 +72,7 @@ class PluginRuntime
virtual unsigned char *GetDataHash(); virtual unsigned char *GetDataHash();
CompiledFunction *GetJittedFunctionByOffset(cell_t pcode_offset); CompiledFunction *GetJittedFunctionByOffset(cell_t pcode_offset);
void AddJittedFunction(CompiledFunction *fn); void AddJittedFunction(CompiledFunction *fn);
void SetName(const char *name); void SetNames(const char *fullname, const char *name);
unsigned GetNativeReplacement(size_t index); unsigned GetNativeReplacement(size_t index);
ScriptedInvoker *GetPublicFunction(size_t index); ScriptedInvoker *GetPublicFunction(size_t index);
int UpdateNativeBinding(uint32_t index, SPVM_NATIVE_FUNC pfn, uint32_t flags, void *data) KE_OVERRIDE; 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 LookupLine(ucell_t addr, uint32_t *line) KE_OVERRIDE;
int LookupFunction(ucell_t addr, const char **name) KE_OVERRIDE; int LookupFunction(ucell_t addr, const char **name) KE_OVERRIDE;
int LookupFile(ucell_t addr, const char **filename) KE_OVERRIDE; int LookupFile(ucell_t addr, const char **filename) KE_OVERRIDE;
const char *GetFilename() KE_OVERRIDE {
return full_name_.chars();
}
PluginContext *GetBaseContext(); PluginContext *GetBaseContext();
@ -89,11 +93,7 @@ class PluginRuntime
return m_JitFunctions[i]; return m_JitFunctions[i];
} }
const char *Name() const { const char *Name() const {
return name_; return name_.chars();
}
static inline size_t offsetToPlugin() {
return 0x0fff0000;
} }
public: public:
@ -117,7 +117,8 @@ class PluginRuntime
ke::AutoPtr<sp::LegacyImage> image_; ke::AutoPtr<sp::LegacyImage> image_;
ke::AutoArray<uint8_t> aligned_code_; ke::AutoArray<uint8_t> aligned_code_;
ke::AutoArray<floattbl_t> float_table_; ke::AutoArray<floattbl_t> float_table_;
ke::AutoArray<char> name_; ke::AString name_;
ke::AString full_name_;
Code code_; Code code_;
Data data_; Data data_;
ke::AutoArray<sp_native_t> natives_; ke::AutoArray<sp_native_t> natives_;

View File

@ -15,6 +15,8 @@
#include <string.h> #include <string.h>
#include "scripted-invoker.h" #include "scripted-invoker.h"
#include "plugin-runtime.h" #include "plugin-runtime.h"
#include "environment.h"
#include "plugin-context.h"
/******************** /********************
* FUNCTION CALLING * * FUNCTION CALLING *
@ -23,43 +25,14 @@
using namespace sp; using namespace sp;
using namespace SourcePawn; 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) 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_errorstate(SP_ERROR_NONE),
m_FnId(id), m_FnId(id),
cc_function_(nullptr) cc_function_(nullptr)
{ {
m_pRuntime = runtime;
runtime->GetPublicByIndex(pub_id, &public_); runtime->GetPublicByIndex(pub_id, &public_);
size_t rt_len = strlen(runtime->Name()); 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); 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) int ScriptedInvoker::PushCell(cell_t cell)
{ {
if (m_curparam >= SP_MAX_EXEC_PARAMS) if (m_curparam >= SP_MAX_EXEC_PARAMS)
@ -169,21 +172,41 @@ ScriptedInvoker::Cancel()
int int
ScriptedInvoker::Execute(cell_t *result) 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 bool
ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result) ScriptedInvoker::Invoke(cell_t *result)
{ {
int err = SP_ERROR_NONE; if (!IsRunnable()) {
if (!IsRunnable())
m_errorstate = SP_ERROR_NOT_RUNNABLE;
if (m_errorstate != SP_ERROR_NONE) {
err = m_errorstate;
Cancel(); 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! //This is for re-entrancy!
@ -191,7 +214,6 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
ParamInfo temp_info[SP_MAX_EXEC_PARAMS]; ParamInfo temp_info[SP_MAX_EXEC_PARAMS];
unsigned int numparams = m_curparam; unsigned int numparams = m_curparam;
unsigned int i; unsigned int i;
bool docopies = true;
if (numparams) if (numparams)
{ {
@ -201,16 +223,19 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
m_curparam = 0; m_curparam = 0;
/* Browse the parameters and build arrays */ /* Browse the parameters and build arrays */
bool ok = true;
for (i=0; i<numparams; i++) { for (i=0; i<numparams; i++) {
/* Is this marked as an array? */ /* Is this marked as an array? */
if (temp_info[i].marked) { if (temp_info[i].marked) {
if (!temp_info[i].str.is_sz) { if (!temp_info[i].str.is_sz) {
/* Allocate a normal/generic array */ /* Allocate a normal/generic array */
if ((err=ctx->HeapAlloc(temp_info[i].size, int err = context_->HeapAlloc(
&(temp_info[i].local_addr), temp_info[i].size,
&(temp_info[i].phys_addr))) &(temp_info[i].local_addr),
!= SP_ERROR_NONE) &(temp_info[i].phys_addr));
{ if (err != SP_ERROR_NONE) {
env_->ReportError(err);
ok = false;
break; break;
} }
if (temp_info[i].orig_addr) 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); size_t cells = (temp_info[i].size + sizeof(cell_t) - 1) / sizeof(cell_t);
/* Allocate the buffer */ /* Allocate the buffer */
if ((err=ctx->HeapAlloc(cells, int err = context_->HeapAlloc(
&(temp_info[i].local_addr), cells,
&(temp_info[i].phys_addr))) &(temp_info[i].local_addr),
!= SP_ERROR_NONE) &(temp_info[i].phys_addr));
{ if (err != SP_ERROR_NONE) {
env_->ReportError(err);
ok = false;
break; break;
} }
/* Copy original string if necessary */ /* Copy original string if necessary */
if ((temp_info[i].str.sz_flags & SM_PARAM_STRING_COPY) && (temp_info[i].orig_addr != NULL)) if ((temp_info[i].str.sz_flags & SM_PARAM_STRING_COPY) && (temp_info[i].orig_addr != NULL))
{ {
/* Cut off UTF-8 properly */ /* Cut off UTF-8 properly */
if (temp_info[i].str.sz_flags & SM_PARAM_STRING_UTF8) { if (temp_info[i].str.sz_flags & SM_PARAM_STRING_UTF8) {
if ((err=ctx->StringToLocalUTF8(temp_info[i].local_addr, context_->StringToLocalUTF8(
temp_info[i].size, temp_info[i].local_addr,
(const char *)temp_info[i].orig_addr, temp_info[i].size,
NULL)) (const char *)temp_info[i].orig_addr,
!= SP_ERROR_NONE) NULL);
{
break;
}
} }
/* Copy a binary blob */ /* Copy a binary blob */
else if (temp_info[i].str.sz_flags & SM_PARAM_STRING_BINARY) 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 */ /* Copy ASCII characters */
else else
{ {
if ((err=ctx->StringToLocal(temp_info[i].local_addr, context_->StringToLocal(
temp_info[i].size, temp_info[i].local_addr,
(const char *)temp_info[i].orig_addr)) temp_info[i].size,
!= SP_ERROR_NONE) (const char *)temp_info[i].orig_addr);
{
break;
}
} }
} }
} /* End array/string calculation */ } /* End array/string calculation */
@ -270,14 +292,11 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
} }
/* Make the call if we can */ /* Make the call if we can */
if (err == SP_ERROR_NONE) { if (ok)
if ((err = CallFunction2(ctx, temp_params, numparams, result)) != SP_ERROR_NONE) ok = context_->Invoke(m_FnId, temp_params, numparams, result);
docopies = false;
} else {
docopies = false;
}
/* i should be equal to the last valid parameter + 1 */ /* i should be equal to the last valid parameter + 1 */
bool docopies = ok;
while (i--) { while (i--) {
if (!temp_info[i].marked) if (!temp_info[i].marked)
continue; continue;
@ -299,17 +318,24 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
} }
} }
if ((err=ctx->HeapPop(temp_info[i].local_addr)) != SP_ERROR_NONE) if (int err = context_->HeapPop(temp_info[i].local_addr))
return err; 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 * IPluginRuntime *
ScriptedInvoker::GetParentRuntime() ScriptedInvoker::GetParentRuntime()
{ {
return m_pRuntime; return context_->runtime();
} }
funcid_t funcid_t

View File

@ -14,12 +14,14 @@
#define _INCLUDE_SOURCEMOD_BASEFUNCTION_H_ #define _INCLUDE_SOURCEMOD_BASEFUNCTION_H_
#include <sp_vm_api.h> #include <sp_vm_api.h>
#include <am-utility.h>
namespace sp { namespace sp {
using namespace SourcePawn; using namespace SourcePawn;
class PluginRuntime; class PluginRuntime;
class PluginContext;
class CompiledFunction; class CompiledFunction;
struct ParamInfo struct ParamInfo
@ -43,17 +45,18 @@ class ScriptedInvoker : public IPluginFunction
~ScriptedInvoker(); ~ScriptedInvoker();
public: public:
virtual int PushCell(cell_t cell); int PushCell(cell_t cell);
virtual int PushCellByRef(cell_t *cell, int flags); int PushCellByRef(cell_t *cell, int flags);
virtual int PushFloat(float number); int PushFloat(float number);
virtual int PushFloatByRef(float *number, int flags); int PushFloatByRef(float *number, int flags);
virtual int PushArray(cell_t *inarray, unsigned int cells, int copyback); int PushArray(cell_t *inarray, unsigned int cells, int copyback);
virtual int PushString(const char *string); int PushString(const char *string);
virtual int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags); int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags);
virtual int Execute(cell_t *result); int Execute(cell_t *result);
virtual void Cancel(); void Cancel();
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result); int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result);
virtual IPluginContext *GetParentContext(); IPluginContext *GetParentContext();
bool Invoke(cell_t *result);
bool IsRunnable(); bool IsRunnable();
funcid_t GetFunctionID(); funcid_t GetFunctionID();
int Execute2(IPluginContext *ctx, cell_t *result); int Execute2(IPluginContext *ctx, cell_t *result);
@ -83,13 +86,15 @@ class ScriptedInvoker : public IPluginFunction
int SetError(int err); int SetError(int err);
private: private:
Environment *env_;
PluginRuntime *m_pRuntime; PluginRuntime *m_pRuntime;
PluginContext *context_;
cell_t m_params[SP_MAX_EXEC_PARAMS]; cell_t m_params[SP_MAX_EXEC_PARAMS];
ParamInfo m_info[SP_MAX_EXEC_PARAMS]; ParamInfo m_info[SP_MAX_EXEC_PARAMS];
unsigned int m_curparam; unsigned int m_curparam;
int m_errorstate; int m_errorstate;
funcid_t m_FnId; funcid_t m_FnId;
char *full_name_; ke::AutoArray<char> full_name_;
sp_public_t *public_; sp_public_t *public_;
CompiledFunction *cc_function_; CompiledFunction *cc_function_;
}; };

View File

@ -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<const intptr_t *>(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<const intptr_t *>(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();
}

View File

@ -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 <sp_vm_api.h>
#include <assert.h>
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_

View File

@ -14,6 +14,7 @@
#include "code-stubs.h" #include "code-stubs.h"
#include "x86-utils.h" #include "x86-utils.h"
#include "jit_x86.h" #include "jit_x86.h"
#include "environment.h"
using namespace sp; using namespace sp;
using namespace SourcePawn; using namespace SourcePawn;
@ -64,6 +65,12 @@ CodeStubs::CompileInvokeStub()
// Align the stack. // Align the stack.
__ andl(esp, 0xfffffff0); __ 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 into plugin (align the stack first).
__ call(ecx); __ call(ecx);
@ -98,17 +105,11 @@ CodeStubs::CompileInvokeStub()
__ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx __ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
__ jmp(&ret); __ jmp(&ret);
Label timeout;
__ bind(&timeout);
__ movl(eax, SP_ERROR_TIMEOUT);
__ jmp(&error);
invoke_stub_ = LinkCode(env_, masm); invoke_stub_ = LinkCode(env_, masm);
if (!invoke_stub_) if (!invoke_stub_)
return false; return false;
return_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + error.offset(); return_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + error.offset();
timeout_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + timeout.offset();
return true; return true;
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
#ifndef _include_sourcepawn_jit_frames_x86_h_
#define _include_sourcepawn_jit_frames_x86_h_
#include <sp_vm_types.h>
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<const JitFrame *>(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<const JitExitFrameForNative *>(
reinterpret_cast<const uint8_t *>(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<const JitExitFrameForHelper *>(exit_sp);
}
bool isCompileFunction() const {
return !!return_address;
}
const JitFrame *prev() const {
return reinterpret_cast<const JitFrame *>(this + 1);
}
};
} // namespace sp
#endif // _include_sourcepawn_jit_frames_x86_h_

View File

@ -236,6 +236,9 @@ Compiler::emit(int *errp)
// an opcode, we bind its corresponding label. // an opcode, we bind its corresponding label.
__ bind(&jump_map_[cip_ - codeseg]); __ bind(&jump_map_[cip_ - codeseg]);
// Save the start of the opcode for emitCipMap().
op_cip_ = cip_;
OPCODE op = (OPCODE)readCell(); OPCODE op = (OPCODE)readCell();
if (!emitOp(op) || error_ != SP_ERROR_NONE) { if (!emitOp(op) || error_ != SP_ERROR_NONE) {
*errp = (error_ == SP_ERROR_NONE) ? SP_ERROR_OUT_OF_MEMORY : error_; *errp = (error_ == SP_ERROR_NONE) ? SP_ERROR_OUT_OF_MEMORY : error_;
@ -244,6 +247,17 @@ Compiler::emit(int *errp)
} }
emitCallThunks(); 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(); emitErrorPaths();
uint8_t *code = LinkCode(env_, masm); uint8_t *code = LinkCode(env_, masm);
@ -255,44 +269,69 @@ Compiler::emit(int *errp)
AutoPtr<FixedArray<LoopEdge>> edges( AutoPtr<FixedArray<LoopEdge>> edges(
new FixedArray<LoopEdge>(backward_jumps_.length())); new FixedArray<LoopEdge>(backward_jumps_.length()));
for (size_t i = 0; i < backward_jumps_.length(); i++) { for (size_t i = 0; i < backward_jumps_.length(); i++) {
edges->at(i).offset = backward_jumps_[i]; const BackwardJump &jump = backward_jumps_[i];
edges->at(i).disp32 = *reinterpret_cast<int32_t *>(code + edges->at(i).offset - 4); 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<FixedArray<CipMapEntry>> cipmap(
new FixedArray<CipMapEntry>(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 static int
InvokePushTracker(PluginContext *cx, uint32_t amount) InvokePushTracker(PluginContext *cx, uint32_t amount)
{ {
return cx->pushTracker(amount); return cx->pushTracker(amount);
} }
// No exit frame - error code is returned directly.
static int static int
InvokePopTrackerAndSetHeap(PluginContext *cx) InvokePopTrackerAndSetHeap(PluginContext *cx)
{ {
return cx->popTrackerAndSetHeap(); return cx->popTrackerAndSetHeap();
} }
// Error code must be checked in the environment.
static cell_t static cell_t
InvokeNativeHelper(PluginContext *cx, ucell_t native_idx, cell_t *params) InvokeNativeHelper(PluginContext *cx, ucell_t native_idx, cell_t *params)
{ {
return cx->invokeNative(native_idx, params); return cx->invokeNative(native_idx, params);
} }
// Error code must be checked in the environment.
static cell_t static cell_t
InvokeBoundNativeHelper(PluginContext *cx, SPVM_NATIVE_FUNC fn, cell_t *params) InvokeBoundNativeHelper(PluginContext *cx, SPVM_NATIVE_FUNC fn, cell_t *params)
{ {
return cx->invokeBoundNative(fn, params); return cx->invokeBoundNative(fn, params);
} }
// No exit frame - error code is returned directly.
static int static int
InvokeGenerateFullArray(PluginContext *cx, uint32_t argc, cell_t *argv, int autozero) InvokeGenerateFullArray(PluginContext *cx, uint32_t argc, cell_t *argv, int autozero)
{ {
return cx->generateFullArray(argc, argv, 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 bool
Compiler::emitOp(OPCODE op) Compiler::emitOp(OPCODE op)
{ {
@ -450,8 +489,16 @@ Compiler::emitOp(OPCODE op)
__ subl(tmp, dat); __ subl(tmp, dat);
__ movl(Operand(frmAddr()), tmp); __ movl(Operand(frmAddr()), tmp);
// Align the stack to 16-bytes (each call adds 4 bytes). // Store the function cip for stack traces.
__ subl(esp, 12); __ 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; break;
case OP_IDXADDR_B: case OP_IDXADDR_B:
@ -769,14 +816,14 @@ Compiler::emitOp(OPCODE op)
// Guard against divide-by-zero. // Guard against divide-by-zero.
__ testl(divisor, divisor); __ 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. // A more subtle case; -INT_MIN / -1 yields an overflow exception.
Label ok; Label ok;
__ cmpl(divisor, -1); __ cmpl(divisor, -1);
__ j(not_equal, &ok); __ j(not_equal, &ok);
__ cmpl(dividend, 0x80000000); __ cmpl(dividend, 0x80000000);
__ j(equal, &error_integer_overflow_); jumpOnError(equal, SP_ERROR_INTEGER_OVERFLOW);
__ bind(&ok); __ bind(&ok);
// Now we can actually perform the divide. // Now we can actually perform the divide.
@ -1078,13 +1125,13 @@ Compiler::emitOp(OPCODE op)
if (amount > 0) { if (amount > 0) {
// Check if the stack went beyond the stack top - usually a compiler error. // Check if the stack went beyond the stack top - usually a compiler error.
__ cmpl(stk, intptr_t(context_->memory() + context_->HeapSize())); __ cmpl(stk, intptr_t(context_->memory() + context_->HeapSize()));
__ j(not_below, &error_stack_min_); jumpOnError(not_below, SP_ERROR_STACKMIN);
} else { } else {
// Check if the stack is going to collide with the heap. // Check if the stack is going to collide with the heap.
__ movl(tmp, Operand(hpAddr())); __ movl(tmp, Operand(hpAddr()));
__ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN)); __ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN));
__ cmpl(stk, tmp); __ cmpl(stk, tmp);
__ j(below, &error_stack_low_); jumpOnError(below, SP_ERROR_STACKLOW);
} }
break; break;
} }
@ -1097,12 +1144,12 @@ Compiler::emitOp(OPCODE op)
if (amount < 0) { if (amount < 0) {
__ cmpl(Operand(hpAddr()), context_->DataSize()); __ cmpl(Operand(hpAddr()), context_->DataSize());
__ j(below, &error_heap_min_); jumpOnError(below, SP_ERROR_HEAPMIN);
} else { } else {
__ movl(tmp, Operand(hpAddr())); __ movl(tmp, Operand(hpAddr()));
__ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN)); __ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN));
__ cmpl(tmp, stk); __ cmpl(tmp, stk);
__ j(above, &error_heap_low_); jumpOnError(above, SP_ERROR_HEAPLOW);
} }
break; break;
} }
@ -1114,7 +1161,7 @@ Compiler::emitOp(OPCODE op)
return false; return false;
if (target->bound()) { if (target->bound()) {
__ jmp32(target); __ jmp32(target);
backward_jumps_.append(masm.pc()); backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
} else { } else {
__ jmp(target); __ jmp(target);
} }
@ -1131,7 +1178,7 @@ Compiler::emitOp(OPCODE op)
__ testl(pri, pri); __ testl(pri, pri);
if (target->bound()) { if (target->bound()) {
__ j32(cc, target); __ j32(cc, target);
backward_jumps_.append(masm.pc()); backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
} else { } else {
__ j(cc, target); __ j(cc, target);
} }
@ -1152,7 +1199,7 @@ Compiler::emitOp(OPCODE op)
__ cmpl(pri, alt); __ cmpl(pri, alt);
if (target->bound()) { if (target->bound()) {
__ j32(cc, target); __ j32(cc, target);
backward_jumps_.append(masm.pc()); backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
} else { } else {
__ j(cc, target); __ j(cc, target);
} }
@ -1171,7 +1218,7 @@ Compiler::emitOp(OPCODE op)
__ call(ExternalAddress((void *)InvokePushTracker)); __ call(ExternalAddress((void *)InvokePushTracker));
__ addl(esp, 8); __ addl(esp, 8);
__ testl(eax, eax); __ testl(eax, eax);
__ j(not_zero, &extern_error_); jumpOnError(not_zero);
__ pop(alt); __ pop(alt);
__ pop(pri); __ pop(pri);
@ -1189,31 +1236,32 @@ Compiler::emitOp(OPCODE op)
__ call(ExternalAddress((void *)InvokePopTrackerAndSetHeap)); __ call(ExternalAddress((void *)InvokePopTrackerAndSetHeap));
__ addl(esp, 4); __ addl(esp, 4);
__ testl(eax, eax); __ testl(eax, eax);
__ j(not_zero, &extern_error_); jumpOnError(not_zero);
__ pop(alt); __ pop(alt);
__ pop(pri); __ pop(pri);
break; 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: case OP_BREAK:
{
cell_t cip = uintptr_t(cip_ - 1) - uintptr_t(rt_->code().bytes);
__ movl(Operand(cipAddr()), cip);
break; break;
}
// This should never be hit.
case OP_HALT: case OP_HALT:
__ align(16); __ align(16);
__ movl(pri, readCell()); __ movl(pri, readCell());
__ jmp(&extern_error_); __ testl(eax, eax);
jumpOnError(not_zero);
break; break;
case OP_BOUNDS: case OP_BOUNDS:
{ {
cell_t value = readCell(); cell_t value = readCell();
__ cmpl(eax, value); __ cmpl(eax, value);
__ j(above, &error_bounds_); jumpOnError(above, SP_ERROR_ARRAY_BOUNDS);
break; break;
} }
@ -1281,7 +1329,7 @@ Compiler::emitCheckAddress(Register reg)
{ {
// Check if we're in memory bounds. // Check if we're in memory bounds.
__ cmpl(reg, context_->HeapSize()); __ 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. // Check if we're in the invalid region between hp and sp.
Label done; Label done;
@ -1289,7 +1337,7 @@ Compiler::emitCheckAddress(Register reg)
__ j(below, &done); __ j(below, &done);
__ lea(tmp, Operand(dat, reg, NoScale)); __ lea(tmp, Operand(dat, reg, NoScale));
__ cmpl(tmp, stk); __ cmpl(tmp, stk);
__ j(below, &error_memaccess_); jumpOnError(below, SP_ERROR_MEMACCESS);
__ bind(&done); __ bind(&done);
} }
@ -1308,7 +1356,7 @@ Compiler::emitGenArray(bool autozero)
__ movl(Operand(hpAddr()), alt); __ movl(Operand(hpAddr()), alt);
__ addl(alt, dat); __ addl(alt, dat);
__ cmpl(alt, stk); __ cmpl(alt, stk);
__ j(not_below, &error_heap_low_); jumpOnError(not_below, SP_ERROR_HEAPLOW);
__ shll(tmp, 2); __ shll(tmp, 2);
__ push(tmp); __ push(tmp);
@ -1318,7 +1366,7 @@ Compiler::emitGenArray(bool autozero)
__ pop(tmp); __ pop(tmp);
__ shrl(tmp, 2); __ shrl(tmp, 2);
__ testl(eax, eax); __ testl(eax, eax);
__ j(not_zero, &extern_error_); jumpOnError(not_zero);
if (autozero) { if (autozero) {
// Note - tmp is ecx and still intact. // Note - tmp is ecx and still intact.
@ -1347,7 +1395,7 @@ Compiler::emitGenArray(bool autozero)
__ pop(tmp); __ pop(tmp);
__ testl(eax, eax); __ testl(eax, eax);
__ j(not_zero, &extern_error_); jumpOnError(not_zero);
// Move tmp back to pri, remove pushed args. // Move tmp back to pri, remove pushed args.
__ movl(pri, tmp); __ movl(pri, tmp);
@ -1367,25 +1415,6 @@ Compiler::emitCall()
return false; 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); CompiledFunction *fun = rt_->GetJittedFunctionByOffset(offset);
if (!fun) { if (!fun) {
// Need to emit a delayed thunk. // Need to emit a delayed thunk.
@ -1398,12 +1427,8 @@ Compiler::emitCall()
__ call(ExternalAddress(fun->GetEntryAddress())); __ call(ExternalAddress(fun->GetEntryAddress()));
} }
// Restore the last cip. // Map the return address to the cip that started this call.
__ movl(Operand(cipAddr()), cip); emitCipMapping(op_cip_);
// Mark us as leaving the last frame.
__ movl(tmp, intptr_t(rt_->GetBaseContext()));
__ subl(Operand(tmp, PluginContext::offsetOfRp()), 1);
return true; return true;
} }
@ -1415,15 +1440,27 @@ Compiler::emitCallThunks()
Label error; Label error;
__ bind(&thunk->call); __ 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)); __ 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 // 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 // on the stack. Allocate a big block so we're aligned.
// 4 because we got here via a call. //
// Note: we add 12 since the push above misaligned the stack.
static const size_t kStackNeeded = 5 * sizeof(void *); 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); __ subl(esp, kStackReserve);
// Set arguments. // Set arguments.
@ -1435,14 +1472,10 @@ Compiler::emitCallThunks()
__ call(ExternalAddress((void *)CompileFromThunk)); __ call(ExternalAddress((void *)CompileFromThunk));
__ movl(edx, Operand(esp, 4 * sizeof(void *))); __ 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); __ testl(eax, eax);
__ j(not_zero, &error); jumpOnError(not_zero);
__ jmp(edx); __ 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); __ 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. // Save registers.
__ push(edx); __ push(edx);
// Push the last parameter for the C++ function. // Push the last parameter for the C++ function.
__ push(stk); __ 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 // Relocate our absolute stk to be dat-relative, and update the context's
// view. // view.
__ subl(stk, dat); __ subl(stk, dat);
__ movl(eax, intptr_t(context_));
__ movl(Operand(eax, PluginContext::offsetOfSp()), stk); __ movl(Operand(eax, PluginContext::offsetOfSp()), stk);
const sp_native_t *native = rt_->GetNative(native_index); const sp_native_t *native = rt_->GetNative(native_index);
@ -1512,11 +1550,14 @@ Compiler::emitNativeCall(OPCODE op)
__ call(ExternalAddress((void *)InvokeBoundNativeHelper)); __ call(ExternalAddress((void *)InvokeBoundNativeHelper));
} }
// Check for errors. // Map the return address to the cip that initiated this call.
__ movl(ecx, intptr_t(rt_->GetBaseContext())); emitCipMapping(op_cip_);
__ movl(ecx, Operand(ecx, PluginContext::offsetOfNativeError()));
__ testl(ecx, ecx); // Check for errors. Note we jump directly to the return stub since the
__ j(not_zero, &extern_error_); // 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. // Restore local state.
__ addl(stk, dat); __ addl(stk, dat);
@ -1631,16 +1672,6 @@ Compiler::emitSwitch()
return true; return true;
} }
void
Compiler::emitErrorPath(Label *dest, int code)
{
if (dest->used()) {
__ bind(dest);
__ movl(eax, code);
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
}
}
void void
Compiler::emitFloatCmp(ConditionCode cc) Compiler::emitFloatCmp(ConditionCode cc)
{ {
@ -1691,22 +1722,112 @@ Compiler::emitFloatCmp(ConditionCode cc)
__ addl(stk, 8); __ 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 void
Compiler::emitErrorPaths() Compiler::emitErrorPaths()
{ {
emitErrorPath(&error_divide_by_zero_, SP_ERROR_DIVIDE_BY_ZERO); // For each path that had an error check, bind it to an error routine and
emitErrorPath(&error_stack_low_, SP_ERROR_STACKLOW); // add it to the cip map. What we'll get is something like:
emitErrorPath(&error_stack_min_, SP_ERROR_STACKMIN); //
emitErrorPath(&error_bounds_, SP_ERROR_ARRAY_BOUNDS); // cmp dividend, 0
emitErrorPath(&error_memaccess_, SP_ERROR_MEMACCESS); // jz error_thunk_0
emitErrorPath(&error_heap_low_, SP_ERROR_HEAPLOW); //
emitErrorPath(&error_heap_min_, SP_ERROR_HEAPMIN); // error_thunk_0:
emitErrorPath(&error_integer_overflow_, SP_ERROR_INTEGER_OVERFLOW); // 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()) { // If there's no error code, it should be in eax. Otherwise we'll jump to
__ bind(&extern_error_); // a path that sets eax to a hardcoded value.
__ movl(eax, intptr_t(rt_->GetBaseContext())); __ bind(&path.label);
__ movl(eax, Operand(eax, PluginContext::offsetOfNativeError())); 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())); __ 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_);
}

View File

@ -33,6 +33,36 @@ class LegacyImage;
class Environment; class Environment;
class CompiledFunction; 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_ERRORCHECKS (1<<0)
#define JIT_INLINE_NATIVES (1<<1) #define JIT_INLINE_NATIVES (1<<1)
#define STACK_MARGIN 64 //8 parameters of safety, I guess #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. #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 struct CallThunk
{ {
Label call; SilentLabel call;
cell_t pcode_offset; cell_t pcode_offset;
CallThunk(cell_t pcode_offset) CallThunk(cell_t pcode_offset)
@ -89,10 +107,9 @@ class Compiler
void emitErrorPath(Label *dest, int code); void emitErrorPath(Label *dest, int code);
void emitErrorPaths(); void emitErrorPaths();
void emitFloatCmp(ConditionCode cc); void emitFloatCmp(ConditionCode cc);
void jumpOnError(ConditionCode cc, int err = 0);
void emitThrowPathIfNeeded(int err);
ExternalAddress cipAddr() {
return ExternalAddress(context_->addressOfCip());
}
ExternalAddress hpAddr() { ExternalAddress hpAddr() {
return ExternalAddress(context_->addressOfHp()); return ExternalAddress(context_->addressOfHp());
} }
@ -100,6 +117,16 @@ class Compiler
return ExternalAddress(context_->addressOfFrm()); 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: private:
AssemblerX86 masm; AssemblerX86 masm;
Environment *env_; Environment *env_;
@ -110,20 +137,19 @@ class Compiler
uint32_t pcode_start_; uint32_t pcode_start_;
const cell_t *code_start_; const cell_t *code_start_;
const cell_t *cip_; const cell_t *cip_;
const cell_t *op_cip_;
const cell_t *code_end_; const cell_t *code_end_;
Label *jump_map_; Label *jump_map_;
ke::Vector<size_t> backward_jumps_; ke::Vector<BackwardJump> backward_jumps_;
// Errors ke::Vector<CipMapEntry> cip_map_;
Label error_bounds_;
Label error_heap_low_; // Errors.
Label error_heap_min_; ke::Vector<ErrorPath> error_paths_;
Label error_stack_low_; Label throw_timeout_;
Label error_stack_min_; Label throw_error_code_[SP_MAX_ERROR_CODES];
Label error_divide_by_zero_; Label report_error_;
Label error_memaccess_; Label return_reported_error_;
Label error_integer_overflow_;
Label extern_error_;
ke::Vector<CallThunk *> thunks_; //:TODO: free ke::Vector<CallThunk *> thunks_; //:TODO: free
}; };