Merge pull request 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);
cell_t tempres = result;
if (hook->pf->Execute(&tempres) == SP_ERROR_NONE)
{
if (tempres > result)

View File

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

View File

@ -1,5 +1,5 @@
/**
* vim: set ts=4 :
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
@ -132,51 +132,6 @@ void DebugReport::GenerateCodeError(IPluginContext *pContext, uint32_t code_addr
}
}
void DebugReport::OnContextExecuteError(IPluginContext *ctx, IContextTrace *error)
{
const char *lastname;
const char *plname = pluginsys->FindPluginByContext(ctx->GetContext())->GetFilename();
int n_err = error->GetErrorCode();
if (n_err != SP_ERROR_NATIVE)
{
g_Logger.LogError("[SM] Plugin encountered error %d: %s",
n_err,
error->GetErrorString());
}
if ((lastname=error->GetLastNative(NULL)) != NULL)
{
const char *custerr;
if ((custerr=error->GetCustomErrorString()) != NULL)
{
g_Logger.LogError("[SM] Native \"%s\" reported: %s", lastname, custerr);
} else {
g_Logger.LogError("[SM] Native \"%s\" encountered a generic error.", lastname);
}
}
if (!error->DebugInfoAvailable())
{
g_Logger.LogError("[SM] Debug mode is not enabled for \"%s\"", plname);
g_Logger.LogError("[SM] To enable debug mode, edit plugin_settings.cfg, or type: sm plugins debug %d on",
_GetPluginIndex(ctx));
return;
}
CallStackInfo stk_info;
int i = 0;
g_Logger.LogError("[SM] Displaying call stack trace for plugin \"%s\":", plname);
while (error->GetTraceInfo(&stk_info))
{
g_Logger.LogError("[SM] [%d] Line %d, %s::%s()",
i++,
stk_info.line,
stk_info.filename,
stk_info.function);
}
}
int DebugReport::_GetPluginIndex(IPluginContext *ctx)
{
int id = 1;
@ -199,3 +154,46 @@ int DebugReport::_GetPluginIndex(IPluginContext *ctx)
return pluginsys->GetPluginCount() + 1;
}
void DebugReport::ReportError(const IErrorReport &report, IFrameIterator &iter)
{
// Find the nearest plugin to blame.
const char *blame = nullptr;
for (; !iter.Done(); iter.Next()) {
if (iter.IsScriptedFrame()) {
IPlugin *plugin = pluginsys->FindPluginByContext(iter.Context()->GetContext());
if (plugin)
blame = plugin->GetFilename();
else
blame = iter.Context()->GetRuntime()->GetFilename();
break;
}
}
iter.Reset();
g_Logger.LogError("[SM] Exception reported: %s", report.Message());
if (blame)
g_Logger.LogError("[SM] Blaming plugin: %s", blame);
g_Logger.LogError("[SM] Call stack trace:");
for (int index = 0; !iter.Done(); iter.Next(), index++) {
const char *fn = iter.FunctionName();
if (!fn)
fn = "<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
void OnSourceModAllInitialized();
public: // IDebugListener
void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error);
void ReportError(const IErrorReport &report, IFrameIterator &iter);
void OnDebugSpew(const char *msg, ...);
public:
void GenerateError(IPluginContext *ctx, cell_t func_idx, int err, const char *message, ...);

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

View File

@ -150,10 +150,11 @@ static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
size_t len;
{
DetectExceptions eh(pContext);
len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1);
if (eh.HasException())
return 0;
}
@ -171,10 +172,11 @@ static cell_t sm_InsertServerCommand(IPluginContext *pContext, const cell_t *par
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
size_t len;
{
DetectExceptions eh(pContext);
len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1);
if (eh.HasException())
return 0;
}
@ -211,10 +213,11 @@ static cell_t sm_ClientCommand(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(params[1]);
char buffer[256];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
size_t len;
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
@ -240,10 +243,10 @@ static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(params[1]);
char buffer[256];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
@ -258,10 +261,12 @@ static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params)
/* Build the format string */
char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
size_t len;
{
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;
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/**
* vim: set ts=4 :
* vim: set ts=4 sw=4 tw=99 noet:
* =============================================================================
* SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
@ -81,18 +81,12 @@ cell_t FakeNativeRouter(IPluginContext *pContext, const cell_t *params, void *pD
s_curparams[i] = params[i];
}
/* Push info and execute. */
// Push info and execute. If Invoke() fails, the error will propagate up.
// We still carry on below to clear our global state.
cell_t result = 0;
native->call->PushCell(pCaller->GetMyHandle());
native->call->PushCell(params[0]);
int error;
if ((error=native->call->Execute(&result)) != SP_ERROR_NONE)
{
if (pContext->GetLastNativeError() == SP_ERROR_NONE)
{
pContext->ThrowNativeErrorEx(error, "Error encountered while processing a dynamic native");
}
}
native->call->Invoke(&result);
/* Restore everything from the stack if necessary */
s_curnative = pSaveNative;
@ -141,15 +135,15 @@ static cell_t ThrowNativeError(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[512];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
s_curcaller->ThrowNativeError("Error encountered while processing a dynamic native");
} else {
s_curcaller->ThrowNativeErrorEx(params[1], "%s", buffer);
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
pContext->ReportError("%s", buffer);
return 0;
}
@ -402,36 +396,32 @@ static cell_t FormatNativeString(IPluginContext *pContext, const cell_t *params)
char *format_buffer;
if (out_param)
{
if ((err=s_curcaller->LocalToString(s_curparams[out_param], &output_buffer)) != SP_ERROR_NONE)
{
return err;
}
} else {
s_curcaller->LocalToString(s_curparams[out_param], &output_buffer);
else
pContext->LocalToString(params[6], &output_buffer);
}
if (fmt_param)
{
if ((err=s_curcaller->LocalToString(s_curparams[fmt_param], &format_buffer)) != SP_ERROR_NONE)
{
return err;
}
} else {
s_curcaller->LocalToString(s_curparams[fmt_param], &format_buffer);
else
pContext->LocalToString(params[7], &format_buffer);
}
/* Get maximum length */
size_t maxlen = (size_t)params[4];
/* Do the format */
size_t written = smcore.atcprintf(output_buffer, maxlen, format_buffer, s_curcaller, s_curparams, &var_param);
size_t written;
{
DetectExceptions eh(pContext);
written = smcore.atcprintf(output_buffer, maxlen, format_buffer, s_curcaller, s_curparams, &var_param);
if (eh.HasException())
return 0;
}
cell_t *addr;
pContext->LocalToPhysAddr(params[5], &addr);
*addr = (cell_t)written;
return s_curcaller->GetLastNativeError();
return SP_ERROR_NONE;
}
//tee hee

View File

@ -765,9 +765,12 @@ static cell_t sm_WriteFileLine(IPluginContext *pContext, const cell_t *params)
int arg = 3;
char buffer[2048];
{
DetectExceptions eh(pContext);
smcore.atcprintf(buffer, sizeof(buffer), fmt, pContext, params, &arg);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
if (eh.HasException())
return 0;
}
if (SystemFile *sysfile = file->AsSystemFile()) {
fprintf(sysfile->fp(), "%s\n", buffer);
@ -797,9 +800,12 @@ static cell_t sm_BuildPath(IPluginContext *pContext, const cell_t *params)
pContext->LocalToString(params[2], &buffer);
pContext->LocalToString(params[4], &fmt);
{
DetectExceptions eh(pContext);
smcore.atcprintf(path, sizeof(path), fmt, pContext, params, &arg);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
if (eh.HasException())
return 0;
}
return g_pSM->BuildPath(Path_SM_Rel, buffer, params[3], "%s", path);
}
@ -808,11 +814,12 @@ static cell_t sm_LogToGame(IPluginContext *pContext, const cell_t *params)
{
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
size_t len;
char buffer[1024];
size_t len = g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
len = g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
if (eh.HasException())
return 0;
}
@ -835,10 +842,10 @@ static cell_t sm_LogMessage(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
if (eh.HasException())
return 0;
}
@ -853,10 +860,10 @@ static cell_t sm_LogError(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
if (eh.HasException())
return 0;
}
@ -900,9 +907,12 @@ static cell_t sm_LogToOpenFile(IPluginContext *pContext, const cell_t *params)
char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
if (eh.HasException())
return 0;
}
IPlugin *pPlugin = pluginsys->FindPluginByContext(pContext->GetContext());
g_Logger.LogToOpenFile(sysfile->fp(), "[%s] %s", pPlugin->GetFilename(), buffer);
@ -922,9 +932,12 @@ static cell_t sm_LogToOpenFileEx(IPluginContext *pContext, const cell_t *params)
char buffer[2048];
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
if (eh.HasException())
return 0;
}
g_Logger.LogToOpenFile(sysfile->fp(), "%s", buffer);
return 1;

View File

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

View File

@ -1087,10 +1087,11 @@ static cell_t _ShowActivity(IPluginContext *pContext,
if (replyto == SM_REPLY_CONSOLE)
{
g_pSM->SetGlobalTarget(client);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1102,10 +1103,11 @@ static cell_t _ShowActivity(IPluginContext *pContext,
else
{
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1141,10 +1143,11 @@ static cell_t _ShowActivity(IPluginContext *pContext,
{
newsign = name;
}
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1165,10 +1168,11 @@ static cell_t _ShowActivity(IPluginContext *pContext,
{
newsign = name;
}
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1210,10 +1214,10 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
}
g_pSM->SetGlobalTarget(client);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1227,10 +1231,10 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
else
{
g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1266,10 +1270,11 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
{
newsign = name;
}
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1290,10 +1295,11 @@ static cell_t _ShowActivity2(IPluginContext *pContext,
{
newsign = name;
}
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, fmt_param);
if (eh.HasException())
return 0;
}
@ -1350,10 +1356,10 @@ static cell_t KickClient(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(client);
char buffer[256];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
@ -1387,10 +1393,10 @@ static cell_t KickClientEx(IPluginContext *pContext, const cell_t *params)
g_pSM->SetGlobalTarget(client);
char buffer[256];
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}

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

View File

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

View File

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

View File

@ -909,10 +909,11 @@ static cell_t sm_ServerCommandEx(IPluginContext *pContext, const cell_t *params)
g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
char buffer[1024];
size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 3);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
size_t len;
{
DetectExceptions eh(pContext);
len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 3);
if (eh.HasException())
return 0;
}
@ -965,10 +966,10 @@ static cell_t FakeClientCommandEx(IPluginContext *pContext, const cell_t *params
g_SourceMod.SetGlobalTarget(params[1]);
char buffer[256];
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}

View File

@ -1,5 +1,5 @@
/**
* vim: set ts=4 :
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
@ -321,11 +321,11 @@ static cell_t PrintToChat(IPluginContext *pContext, const cell_t *params)
g_SourceMod.SetGlobalTarget(client);
char buffer[192];
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
/* Check for an error before printing to the client */
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
@ -355,11 +355,11 @@ static cell_t PrintCenterText(IPluginContext *pContext, const cell_t *params)
g_SourceMod.SetGlobalTarget(client);
char buffer[192];
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
/* Check for an error before printing to the client */
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}
@ -389,11 +389,10 @@ static cell_t PrintHintText(IPluginContext *pContext, const cell_t *params)
g_SourceMod.SetGlobalTarget(client);
char buffer[192];
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
/* Check for an error before printing to the client */
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
if (eh.HasException())
return 0;
}

View File

@ -1,5 +1,5 @@
/**
* vim: set ts=4 :
* vim: set ts=4 sw=4 tw=99 noet:
* =============================================================================
* SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
@ -415,9 +415,11 @@ static cell_t ShowSyncHudText(IPluginContext *pContext, const cell_t *params)
}
g_SourceMod.SetGlobalTarget(client);
g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3);
if (eh.HasException())
return 0;
}
@ -488,9 +490,11 @@ static cell_t ShowHudText(IPluginContext *pContext, const cell_t *params)
}
g_SourceMod.SetGlobalTarget(client);
g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3);
if (pContext->GetLastNativeError() != SP_ERROR_NONE)
{
DetectExceptions eh(pContext);
g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3);
if (eh.HasException())
return 0;
}

View File

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

View File

@ -290,8 +290,7 @@ DataStatus DecodeValveParam(IPluginContext *pContext,
case Valve_Vector:
{
cell_t *addr;
int err;
err = pContext->LocalToPhysAddr(param, &addr);
pContext->LocalToPhysAddr(param, &addr);
unsigned char *mem = (unsigned char *)buffer;
if (data->type == PassType_Basic)
@ -317,12 +316,6 @@ DataStatus DecodeValveParam(IPluginContext *pContext,
}
}
if (err != SP_ERROR_NONE)
{
pContext->ThrowNativeErrorEx(err, "Could not read plugin data");
return Data_Fail;
}
/* Use placement new to initialize the object cleanly
* This has no destructor so we don't need to do
* DestroyValveParam() or something :]

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;
}
T *buffer() const {
return data_;
}
private:
FixedArray(const FixedArray &other) KE_DELETE;
FixedArray &operator =(const FixedArray &other) KE_DELETE;

View File

@ -230,13 +230,24 @@ class Label
assert(this->offset() == offset);
}
private:
protected:
// Note that 0 as an invalid offset is okay, because the offset we save for
// pending jumps are after the jump opcode itself, and therefore 0 is never
// valid, since there are no 0-byte jumps.
uint32_t status_;
};
// Label that suppresses its assert, for non-stack use.
class SilentLabel : public Label
{
public:
SilentLabel()
{}
~SilentLabel() {
status_ = 0;
}
};
// A DataLabel is a special form of Label intended for absolute addresses that
// are within the code buffer, and thus aren't known yet, and will be
// automatically fixed up when calling emitToExecutableMemory().

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
//
@ -19,15 +19,19 @@
*/
#include <stdio.h>
#include <assert.h>
#include "sp_vm_types.h"
/** SourcePawn Engine API Versions */
#define SOURCEPAWN_ENGINE2_API_VERSION 9
#define SOURCEPAWN_API_VERSION 0x0209
#define SOURCEPAWN_ENGINE2_API_VERSION 0xA
#define SOURCEPAWN_API_VERSION 0x020A
namespace SourceMod {
struct IdentityToken_t;
};
namespace sp {
class Environment;
};
struct sp_context_s;
typedef struct sp_context_s sp_context_t;
@ -36,6 +40,8 @@ namespace SourcePawn
{
class IVirtualMachine;
class IPluginRuntime;
class ISourcePawnEngine2;
class ISourcePawnEnvironment;
/* Parameter flags */
#define SM_PARAM_COPYBACK (1<<0) /**< Copy an array/reference back after call */
@ -162,21 +168,23 @@ namespace SourcePawn
* @brief Executes the function, resets the pushed parameter list, and
* performs any copybacks.
*
* The exception state is reset upon entering and leaving this
* function. Callers that want to propagate exceptions from Execute()
* should use Invoke(). ReportError is not preferred since it would
* lose any custom exception messages.
*
* @param result Pointer to store return value in.
* @return Error code, if any.
*/
virtual int Execute(cell_t *result) =0;
/**
* @brief Executes the function with the given parameter array.
* Parameters are read in forward order (i.e. index 0 is parameter #1)
* NOTE: You will get an error if you attempt to use CallFunction() with
* previously pushed parameters.
* @brief This function is deprecated. If invoked, it reports an error.
*
* @param params Array of cell parameters.
* @param num_params Number of parameters to push.
* @param result Pointer to store result of function on return.
* @return SourcePawn error code (if any).
* @param params Unused.
* @param num_params Unused.
* @param result Unused.
* @return SP_ERROR_ABORTED.
*/
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) =0;
@ -204,30 +212,22 @@ namespace SourcePawn
virtual funcid_t GetFunctionID() =0;
/**
* @brief Executes the forward, resets the pushed parameter list, and
* performs any copybacks.
* @brief This function is deprecated. If invoked, it reports an error.
*
* Note: A function can only be executed given a runtime it was created in.
*
* @param ctx Context to execute the function in.
* @param result Pointer to store return value in.
* @return Error code, if any.
* @param ctx Unused.
* @param result Unused.
* @return SP_ERROR_ABORTED.
*/
virtual int Execute2(IPluginContext *ctx, cell_t *result) =0;
/**
* @brief Executes the function with the given parameter array.
* Parameters are read in forward order (i.e. index 0 is parameter #1)
* NOTE: You will get an error if you attempt to use CallFunction() with
* previously pushed parameters.
* @brief This function is deprecated. If invoked, it reports an error.
*
* Note: A function can only be executed given a runtime it was created in.
*
* @param ctx Context to execute the function in.
* @param params Array of cell parameters.
* @param num_params Number of parameters to push.
* @param result Pointer to store result of function on return.
* @return SourcePawn error code (if any).
* @param ctx Unused.
* @param params Unused.
* @param num_params Unused.
* @param result Unused.
* @return SP_ERROR_ABORTED.
*/
virtual int CallFunction2(IPluginContext *ctx,
const cell_t *params,
@ -240,6 +240,20 @@ namespace SourcePawn
* @return IPluginRuntime pointer.
*/
virtual IPluginRuntime *GetParentRuntime() =0;
/**
* @brief Executes the function, resets the pushed parameter list, and
* performs any copybacks.
*
* Unlike Execute(), this does not reset the exception state. It is
* illegal to call Invoke() while an exception is unhandled. If it
* returns false, an exception has been thrown, and must either be
* handled via ExceptionHandler or propagated to its caller.
*
* @param result Pointer to store return value in.
* @return True on success, false on error.
*/
virtual bool Invoke(cell_t *rval = nullptr) = 0;
};
@ -464,6 +478,11 @@ namespace SourcePawn
* @return Native pointer, or NULL on failure.
*/
virtual const sp_native_t *GetNative(uint32_t index) = 0;
/**
* @brief Return the file or location this plugin was loaded from.
*/
virtual const char *GetFilename() = 0;
};
/**
@ -734,6 +753,8 @@ namespace SourcePawn
/**
* @brief Throws a error and halts any current execution.
*
* This function is deprecated. Use ReportError() instead.
*
* @param error The error number to set.
* @param msg Custom error message format. NULL to use default.
* @param ... Message format arguments, if any.
@ -744,6 +765,8 @@ namespace SourcePawn
/**
* @brief Throws a generic native error and halts any current execution.
*
* This function is deprecated. Use ReportError() instead.
*
* @param msg Custom error message format. NULL to set no message.
* @param ... Message format arguments, if any.
* @return 0 for convenience.
@ -816,14 +839,13 @@ namespace SourcePawn
virtual IPluginRuntime *GetRuntime() =0;
/**
* @brief Executes a function in the context. The function must be
* a member of the context's parent runtime.
* @brief This function is deprecated. If invoked, it reports an error.
*
* @param function Function.
* @param params Parameters.
* @param num_params Number of parameters in the parameter array.
* @param result Optional pointer to store the result on success.
* @return Error code.
* @param function Unused.
* @param params Unused.
* @param num_params Unused.
* @param result Unused.
* @return SP_ERROR_ABORTED.
*/
virtual int Execute2(IPluginFunction *function,
const cell_t *params,
@ -833,8 +855,11 @@ namespace SourcePawn
/**
* @brief Returns whether a context is in an error state.
*
* This function is deprecated. Use DetectExceptions instead.
*
* This should only be used inside natives to determine whether
* a prior call failed.
* a prior call failed. The return value should only be compared
* against SP_ERROR_NONE.
*/
virtual int GetLastNativeError() =0;
@ -870,81 +895,171 @@ namespace SourcePawn
virtual bool GetKey(int k, void **value) =0;
/**
* @brief Clears the last native error.
* @brief If an exception is pending, this removes the exception. It
* is deprecated and should not be used.
*/
virtual void ClearLastNativeError() =0;
};
/**
* @brief Information about a position in a call stack.
* @brief Return a pointer to the ISourcePawnEngine2 that is active.
* This is a convenience function.
*
* @return API pointer.
*/
struct CallStackInfo
{
const char *filename; /**< NULL if not found */
unsigned int line; /**< 0 if not found */
const char *function; /**< NULL if not found */
virtual ISourcePawnEngine2 *APIv2() = 0;
/**
* @brief Report an error.
*
* @param message Error message format.
* @param ... Formatting arguments.
*/
virtual void ReportError(const char *fmt, ...) = 0;
/**
* @brief Report an error with variadic arguments.
*
* @param message Error message format.
* @param ap Formatting arguments.
*/
virtual void ReportErrorVA(const char *fmt, va_list ap) = 0;
/**
* @brief Report a fatal error. Fatal errors cannot be caught by any
* exception handler.
*
* @param message Error message format.
* @param ... Formatting arguments.
*/
virtual void ReportFatalError(const char *fmt, ...) = 0;
/**
* @brief Report a fatal error with variadic arguments. Fatal errors
* cannot be caught by any exception handler.
*
* @param message Error message format.
* @param ap Formatting arguments.
*/
virtual void ReportFatalErrorVA(const char *fmt, va_list ap) = 0;
/**
* @brief Report an error by its builtin number.
*
* @param number Error number.
*/
virtual void ReportErrorNumber(int error) = 0;
};
/**
* @brief Retrieves error information from a debug hook.
* @brief Removed.
*/
class IContextTrace
class IContextTrace;
/**
* @brief Information about a reported error.
*/
class IErrorReport
{
public:
/**
* @brief Returns the integer error code.
* @brief Return the message of the error report.
*
* @return Integer error code.
* @return Message string.
*/
virtual int GetErrorCode() =0;
virtual const char *Message() const = 0;
/**
* @brief Returns a string describing the error.
* @brief True if the error is fatal and cannot be handled (though
* reporting can be suppressed).
*
* @return Error string.
* @return True if fatal, false otherwise.
*/
virtual const char *GetErrorString() =0;
virtual bool IsFatal() const = 0;
/**
* @brief Returns whether debug info is available.
* @brief Return the plugin context that caused the error.
*
* @return True if debug info is available, false otherwise.
* @return Plugin context.
*/
virtual bool DebugInfoAvailable() =0;
/**
* @brief Returns a custom error message.
*
* @return A pointer to a custom error message, or NULL otherwise.
*/
virtual const char *GetCustomErrorString() =0;
/**
* @brief Returns trace info for a specific point in the backtrace, if any.
* The next subsequent call to GetTraceInfo() will return the next item in the call stack.
* Calls are retrieved in descending order (i.e. the first item is at the top of the stack/call sequence).
*
* @param trace An ErrorTraceInfo buffer to store information (NULL to ignore).
* @return True if successful, false if there are no more traces.
*/
virtual bool GetTraceInfo(CallStackInfo *trace) =0;
/**
* @brief Resets the trace to its original position (the call on the top of the stack).
*/
virtual void ResetTrace() =0;
/**
* @brief Retrieves the name of the last native called.
* Returns NULL if there was no native that caused the error.
*
* @param index Optional pointer to store index.
* @return Native name, or NULL if none.
*/
virtual const char *GetLastNative(uint32_t *index) =0;
virtual IPluginContext *Context() const = 0;
};
/**
* @brief Allows inspecting the stack frames of the SourcePawn environment.
*
* Invoking VM functions while iterating frames will cause the iterator
* to become corrupt.
*
* Frames iterate in most-recent to least-recent order.
*/
class IFrameIterator
{
public:
/**
* @brief Returns whether or not there are more frames to read.
*
* @return True if there are more frames to read, false otherwise.
*/
virtual bool Done() const = 0;
/**
* @brief Advances to the next frame.
*
* Note that the iterator starts at either a valid frame or no frame.
*/
virtual void Next() = 0;
/**
* @brief Resets the iterator to the top of the stack.
*/
virtual void Reset() = 0;
/**
* @brief Returns the context owning the current frame, if any.
*
* @return Context, or null.
*/
virtual IPluginContext *Context() const = 0;
/**
* @brief Returns whether or not the current frame is a native frame. If it
* is, line numbers and file paths are not available.
*
* @return True if a native frame, false otherwise.
*/
virtual bool IsNativeFrame() const = 0;
/**
* @brief Returns true if the frame is a scripted frame.
*
* @return True if a scripted frame, false otherwise.
*/
virtual bool IsScriptedFrame() const = 0;
/**
* @brief Returns the line number of the current frame, or 0 if none is
* available.
*
* @return Line number on success, 0 on failure.
*/
virtual unsigned LineNumber() const = 0;
/**
* @brief Returns the function name of the current frame, or null if
* none could be computed.
*
* @return Function name on success, null on failure.
*/
virtual const char *FunctionName() const = 0;
/**
* @brief Returns the file path of the function of the current frame,
* or none could be computed.
*
* @return File path on success, null on failure.
*/
virtual const char *FilePath() const = 0;
};
/**
* @brief Provides callbacks for debug information.
@ -953,12 +1068,13 @@ namespace SourcePawn
{
public:
/**
* @brief Invoked on a context execution error.
* @brief No longer invoked.
*
* @param ctx Context.
* @param error Object holding error information and a backtrace.
* @param ctx Unused.
* @param error Unused.
*/
virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) =0;
virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error)
{}
/**
* @brief Called on debug spew.
@ -967,6 +1083,15 @@ namespace SourcePawn
* @param fmt Message formatting arguments (printf-style).
*/
virtual void OnDebugSpew(const char *msg, ...) =0;
/**
* @brief Called when an error is reported and no exception
* handler was available.
*
* @param report Error report object.
* @param iter Stack frame iterator.
*/
virtual void ReportError(const IErrorReport &report, IFrameIterator &iter) = 0;
};
/**
@ -1114,10 +1239,10 @@ namespace SourcePawn
virtual void ExecFree(void *address) =0;
/**
* @brief Sets the debug listener. This should only be called once.
* If called successively (using manual chaining), only the last function should
* attempt to call back into the same plugin. Otherwise, globally cached states
* can be accidentally overwritten.
* @brief Sets the debug listener.
*
* This should be called once on application startup. It is
* not considered part of the userland API and may change at any time.
*
* @param listener Pointer to an IDebugListener.
* @return Old IDebugListener, or NULL if none.
@ -1166,6 +1291,8 @@ namespace SourcePawn
virtual void FreePageMemory(void *ptr) =0;
};
class ExceptionHandler;
/**
* @brief Outlines the interface a Virtual Machine (JIT) must expose
*/
@ -1228,10 +1355,10 @@ namespace SourcePawn
virtual void DestroyFakeNative(SPVM_NATIVE_FUNC func) =0;
/**
* @brief Sets the debug listener. This should only be called once.
* If called successively (using manual chaining), only the last function should
* attempt to call back into the same plugin. Otherwise, globally cached states
* can be accidentally overwritten.
* @brief Sets the debug listener.
*
* This should be called once on application startup. It is
* not considered part of the userland API and may change at any time.
*
* @param listener Pointer to an IDebugListener.
* @return Old IDebugListener, or NULL if none.
@ -1248,6 +1375,9 @@ namespace SourcePawn
/**
* @brief Returns the string representation of an error message.
*
* This function is deprecated and should not be used. The exception
* handling API should be used instead.
*
* @param err Error code.
* @return Error string, or NULL if not found.
*/
@ -1326,6 +1456,11 @@ namespace SourcePawn
* @return New runtime pointer, or NULL on failure.
*/
virtual IPluginRuntime *LoadBinaryFromFile(const char *file, char *error, size_t maxlength) = 0;
/**
* @brief Returns the environment.
*/
virtual ISourcePawnEnvironment *Environment() = 0;
};
// @brief This class is the v3 API for SourcePawn. It provides access to
@ -1351,6 +1486,23 @@ namespace SourcePawn
// all plugin memory. This should not be called while plugins have
// active code running on the stack.
virtual void Shutdown() = 0;
// @brief Enters an exception handling scope. This is intended to be
// used on the stack and must have a corresponding call to
// LeaveExceptionHandlingScope. When in an exception handling scope,
// exceptions are not immediately reported. Instead the caller is
// responsible for propagation them or clearing them.
virtual void EnterExceptionHandlingScope(ExceptionHandler *handler) = 0;
// @brief Leaves the most recent exception handling scope. The handler
// is provided as a sanity check.
virtual void LeaveExceptionHandlingScope(ExceptionHandler *handler) = 0;
// @brief Returns whether or not an exception is currently pending.
virtual bool HasPendingException(const ExceptionHandler *handler) = 0;
// @brief Returns the message of the pending exception.
virtual const char *GetPendingExceptionMessage(const ExceptionHandler *handler) = 0;
};
// @brief This class is the entry-point to using SourcePawn from a DLL.
@ -1371,6 +1523,96 @@ namespace SourcePawn
// @brief A function named "GetSourcePawnFactory" is exported from the
// SourcePawn DLL, conforming to the following signature:
typedef ISourcePawnFactory *(*GetSourcePawnFactoryFn)(int apiVersion);
// @brief A helper class for handling exceptions.
//
// ExceptionHandlers can be used to detect, catch, and re-throw VM errors
// within C++ code.
//
// When throwing errors, SourcePawn creates an exception object. The
// exception object is global state. As long as an exception is present,
// all scripted code should immediately abort and return to their callers,
// all native code should exit, all code should propagate any error states
// until the exception is handled.
//
// In some cases, an error code is not available. For example, if a native
// detects an exception, it does not have an error status to propagate. It
// should simply return instead. The return value will be ignored; the VM
// knows to abort the script.
class ExceptionHandler
{
friend class sp::Environment;
public:
ExceptionHandler(ISourcePawnEngine2 *api)
: env_(api->Environment()),
catch_(true)
{
env_->EnterExceptionHandlingScope(this);
}
ExceptionHandler(IPluginContext *ctx)
: env_(ctx->APIv2()->Environment()),
catch_(true)
{
env_->EnterExceptionHandlingScope(this);
}
~ExceptionHandler()
{
env_->LeaveExceptionHandlingScope(this);
}
virtual uint32_t ApiVersion() const {
return SOURCEPAWN_API_VERSION;
}
// Propagates the exception instead of catching it. After calling this,
// no more SourcePawn code should be executed until the exception is
// handled. Callers should return immediately.
void Rethrow() {
assert(catch_ && HasException());
catch_ = false;
}
bool HasException() const {
return env_->HasPendingException(this);
}
const char *Message() const {
return env_->GetPendingExceptionMessage(this);
}
private:
// Don't allow heap construction.
ExceptionHandler(const ExceptionHandler &other);
void operator =(const ExceptionHandler &other);
void *operator new(size_t size);
void operator delete(void *, size_t);
private:
ISourcePawnEnvironment *env_;
ExceptionHandler *next_;
protected:
// If true, the exception will be swallowed.
bool catch_;
};
// @brief An implementation of ExceptionHandler that simply collects
// whether an exception was thrown.
class DetectExceptions : public ExceptionHandler
{
public:
DetectExceptions(ISourcePawnEngine2 *api)
: ExceptionHandler(api)
{
catch_ = false;
}
DetectExceptions(IPluginContext *ctx)
: ExceptionHandler(ctx)
{
catch_ = false;
}
};
};
#endif //_INCLUDE_SOURCEPAWN_VM_API_H_

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_INTEGER_OVERFLOW 29 /**< Integer overflow (-INT_MIN / -1) */
#define SP_ERROR_TIMEOUT 30 /**< Timeout */
#define SP_ERROR_USER 31 /**< Custom message */
#define SP_ERROR_FATAL 32 /**< Custom fatal message */
#define SP_MAX_ERROR_CODES 33
//Hey you! Update the string table if you add to the end of me! */
/**********************************************

View File

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

View File

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

View File

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

View File

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

View File

@ -16,10 +16,15 @@
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),
code_length_(code_length),
code_offset_(pcode_offs),
edges_(edges)
edges_(edges),
cip_map_(cipmap)
{
}
@ -27,3 +32,40 @@ CompiledFunction::~CompiledFunction()
{
Environment::get()->FreeCode(entry_);
}
static int cip_map_entry_cmp(const void *a1, const void *aEntry)
{
uint32_t pcoffs = (uint32_t)a1;
const CipMapEntry *entry = reinterpret_cast<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
{
// Offset to the patchable jump instruction, such that (base + offset - 4)
// yields a patchable location.
uint32_t offset;
// The displacement to either the timeout routine or the original
// displacement, depending on the timeout state.
int32_t disp32;
};
struct CipMapEntry {
// Offset from the first cip of the function.
uint32_t cipoffs;
// Offset from the first pc of the function.
uint32_t pcoffs;
};
static const ucell_t kInvalidCip = 0xffffffff;
class CompiledFunction
{
public:
CompiledFunction(void *entry_addr, cell_t pcode_offs, FixedArray<LoopEdge> *edges);
CompiledFunction(void *entry_addr,
size_t code_length,
cell_t pcode_offs,
FixedArray<LoopEdge> *edges,
FixedArray<CipMapEntry> *cip_map);
~CompiledFunction();
public:
@ -43,14 +60,18 @@ class CompiledFunction
uint32_t NumLoopEdges() const {
return edges_->length();
}
const LoopEdge &GetLoopEdge(size_t i) const {
LoopEdge &GetLoopEdge(size_t i) {
return edges_->at(i);
}
ucell_t FindCipByPc(void *pc);
private:
void *entry_;
size_t code_length_;
cell_t code_offset_;
AutoPtr<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 "dll_exports.h"
#include "environment.h"
#include "stack-frames.h"
using namespace ke;
using namespace sp;
@ -55,72 +56,36 @@ public:
} sFactory;
#ifdef SPSHELL
template <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;
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
{
public:
void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) {
int n_err = error->GetErrorCode();
if (n_err != SP_ERROR_NATIVE)
{
fprintf(stderr, "plugin error: %s\n", error->GetErrorString());
}
if (const char *lastname = error->GetLastNative(NULL))
{
if (const char *custerr = error->GetCustomErrorString())
{
fprintf(stderr, "Native \"%s\" reported: %s", lastname, custerr);
} else {
fprintf(stderr, "Native \"%s\" encountered a generic error.", lastname);
}
}
if (!error->DebugInfoAvailable())
{
fprintf(stderr, "Debug info not available!\n");
return;
}
CallStackInfo stk_info;
int i = 0;
fprintf(stderr, "Displaying call stack trace:\n");
while (error->GetTraceInfo(&stk_info))
{
fprintf(stderr,
" [%d] Line %d, %s::%s()\n",
i++,
stk_info.line,
stk_info.filename,
stk_info.function);
}
void ReportError(const IErrorReport &report, IFrameIterator &iter) KE_OVERRIDE {
fprintf(stdout, "Exception thrown: %s\n", report.Message());
DumpStack(iter);
}
void OnDebugSpew(const char *msg, ...) {
@ -181,18 +146,43 @@ static cell_t PrintFloat(IPluginContext *cx, const cell_t *params)
return printf("%f\n", sp_ctof(params[1]));
}
static int Execute(const char *file)
static cell_t DoExecute(IPluginContext *cx, const cell_t *params)
{
ICompilation *co = sEnv->APIv2()->StartCompilation();
if (!co) {
fprintf(stderr, "Could not create a compilation context\n");
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;
}
int err;
AutoT<IPluginRuntime> rt(sEnv->APIv2()->LoadPlugin(co, file, &err));
static cell_t DumpStackTrace(IPluginContext *cx, const cell_t *params)
{
FrameIterator iter;
DumpStack(iter);
return 0;
}
static int Execute(const char *file)
{
char error[255];
AutoPtr<IPluginRuntime> rt(sEnv->APIv2()->LoadBinaryFromFile(file, error, sizeof(error)));
if (!rt) {
fprintf(stderr, "Could not load plugin: %s\n", sEnv->GetErrorString(err));
fprintf(stderr, "Could not load plugin: %s\n", error);
return 1;
}
@ -201,6 +191,9 @@ static int Execute(const char *file)
BindNative(rt, "printnums", PrintNums);
BindNative(rt, "printfloat", PrintFloat);
BindNative(rt, "donothing", DoNothing);
BindNative(rt, "execute", DoExecute);
BindNative(rt, "invoke", DoInvoke);
BindNative(rt, "dump_stack_trace", DumpStackTrace);
IPluginFunction *fun = rt->GetFunctionByName("main");
if (!fun)
@ -208,11 +201,14 @@ static int Execute(const char *file)
IPluginContext *cx = rt->GetDefaultContext();
int result = fun->Execute2(cx, &err);
if (err != SP_ERROR_NONE) {
fprintf(stderr, "Error executing main(): %s\n", sEnv->GetErrorString(err));
int result;
{
ExceptionHandler eh(cx);
if (!fun->Invoke(&result)) {
fprintf(stderr, "Error executing main: %s\n", eh.Message());
return 1;
}
}
return result;
}

View File

@ -13,7 +13,6 @@
#include "environment.h"
#include "x86/jit_x86.h"
#include "watchdog_timer.h"
#include "debug-trace.h"
#include "api.h"
#include "code-stubs.h"
#include "watchdog_timer.h"
@ -25,10 +24,12 @@ static Environment *sEnvironment = nullptr;
Environment::Environment()
: debugger_(nullptr),
exception_code_(SP_ERROR_NONE),
profiler_(nullptr),
jit_enabled_(true),
profiling_enabled_(false),
code_pool_(nullptr)
code_pool_(nullptr),
top_(nullptr)
{
}
@ -150,7 +151,9 @@ static const char *sErrorMsgTable[] =
"Plugin format is too new",
"Out of memory",
"Integer overflow",
"Script execution timed out"
"Script execution timed out",
"Custom error",
"Fatal error"
};
const char *
@ -161,17 +164,6 @@ Environment::GetErrorString(int error)
return sErrorMsgTable[error];
}
void
Environment::ReportError(PluginRuntime *runtime, int err, const char *errstr, cell_t rp_start)
{
if (!debugger_)
return;
CContextTrace trace(runtime, err, errstr, rp_start);
debugger_->OnContextExecuteError(runtime->GetDefaultContext(), &trace);
}
void *
Environment::AllocateCode(size_t size)
{
@ -198,6 +190,15 @@ Environment::DeregisterRuntime(PluginRuntime *rt)
runtimes_.remove(rt);
}
static inline void
SwapLoopEdge(uint8_t *code, LoopEdge &e)
{
int32_t *loc = reinterpret_cast<int32_t *>(code + e.offset - 4);
int32_t new_disp32 = e.disp32;
e.disp32 = *loc;
*loc = new_disp32;
}
void
Environment::PatchAllJumpsForTimeout()
{
@ -208,11 +209,8 @@ Environment::PatchAllJumpsForTimeout()
CompiledFunction *fun = rt->GetJitFunction(i);
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
const LoopEdge &e = fun->GetLoopEdge(j);
int32_t diff = intptr_t(code_stubs_->TimeoutStub()) - intptr_t(base + e.offset);
*reinterpret_cast<int32_t *>(base + e.offset - 4) = diff;
}
for (size_t j = 0; j < fun->NumLoopEdges(); j++)
SwapLoopEdge(base, fun->GetLoopEdge(j));
}
}
}
@ -227,10 +225,8 @@ Environment::UnpatchAllJumpsFromTimeout()
CompiledFunction *fun = rt->GetJitFunction(i);
uint8_t *base = reinterpret_cast<uint8_t *>(fun->GetEntryAddress());
for (size_t j = 0; j < fun->NumLoopEdges(); j++) {
const LoopEdge &e = fun->GetLoopEdge(j);
*reinterpret_cast<int32_t *>(base + e.offset - 4) = e.disp32;
}
for (size_t j = 0; j < fun->NumLoopEdges(); j++)
SwapLoopEdge(base, fun->GetLoopEdge(j));
}
}
}
@ -238,16 +234,168 @@ Environment::UnpatchAllJumpsFromTimeout()
int
Environment::Invoke(PluginRuntime *runtime, CompiledFunction *fn, cell_t *result)
{
// Must be in an invoke frame.
assert(top_ && top_->cx() == runtime->GetBaseContext());
PluginContext *cx = runtime->GetBaseContext();
// Note that cip, hp, sp are saved and restored by Execute2().
*cx->addressOfCip() = fn->GetCodeOffset();
InvokeStubFn invoke = code_stubs_->InvokeStub();
EnterInvoke();
int err = invoke(cx, fn->GetEntryAddress(), result);
LeaveInvoke();
return err;
return invoke(cx, fn->GetEntryAddress(), result);
}
void
Environment::ReportError(int code)
{
const char *message = GetErrorString(code);
if (!message) {
char buffer[255];
UTIL_Format(buffer, sizeof(buffer), "Unknown error code %d", code);
ReportError(code, buffer);
} else {
ReportError(code, message);
}
}
class ErrorReport : public SourcePawn::IErrorReport
{
public:
ErrorReport(int code, const char *message, PluginContext *cx)
: code_(code),
message_(message),
context_(cx)
{}
const char *Message() const KE_OVERRIDE {
return message_;
}
bool IsFatal() const KE_OVERRIDE {
switch (code_) {
case SP_ERROR_HEAPLOW:
case SP_ERROR_INVALID_ADDRESS:
case SP_ERROR_STACKLOW:
case SP_ERROR_INVALID_INSTRUCTION:
case SP_ERROR_MEMACCESS:
case SP_ERROR_STACKMIN:
case SP_ERROR_HEAPMIN:
case SP_ERROR_INSTRUCTION_PARAM:
case SP_ERROR_STACKLEAK:
case SP_ERROR_HEAPLEAK:
case SP_ERROR_TRACKER_BOUNDS:
case SP_ERROR_PARAMS_MAX:
case SP_ERROR_ABORTED:
case SP_ERROR_OUT_OF_MEMORY:
case SP_ERROR_FATAL:
return true;
default:
return false;
}
}
IPluginContext *Context() const KE_OVERRIDE {
return context_;
}
private:
int code_;
const char *message_;
PluginContext *context_;
};
void
Environment::ReportErrorVA(const char *fmt, va_list ap)
{
ReportErrorVA(SP_ERROR_USER, fmt, ap);
}
void
Environment::ReportErrorVA(int code, const char *fmt, va_list ap)
{
// :TODO: right-size the string rather than rely on this buffer.
char buffer[1024];
UTIL_FormatVA(buffer, sizeof(buffer), fmt, ap);
ReportError(code, buffer);
}
void
Environment::ReportErrorFmt(int code, const char *message, ...)
{
va_list ap;
va_start(ap, message);
ReportErrorVA(code, message, ap);
va_end(ap);
}
void
Environment::ReportError(int code, const char *message)
{
FrameIterator iter;
ErrorReport report(code, message, top_ ? top_->cx() : nullptr);
// If this fires, someone forgot to propagate an error.
assert(!hasPendingException());
// Save the exception state.
if (eh_top_) {
exception_code_ = code;
UTIL_Format(exception_message_, sizeof(exception_message_), "%s", message);
}
// For now, we always report exceptions even if they might be handled.
if (debugger_)
debugger_->ReportError(report, iter);
}
void
Environment::EnterExceptionHandlingScope(ExceptionHandler *handler)
{
handler->next_ = eh_top_;
eh_top_ = handler;
}
void
Environment::LeaveExceptionHandlingScope(ExceptionHandler *handler)
{
assert(handler == eh_top_);
eh_top_ = eh_top_->next_;
// To preserve compatibility with older API, we clear the exception state
// when there is no EH handler.
if (!eh_top_ || handler->catch_)
exception_code_ = SP_ERROR_NONE;
}
bool
Environment::HasPendingException(const ExceptionHandler *handler)
{
// Note here and elsewhere - this is not a sanity assert. In the future, the
// API may need to query the handler.
assert(handler == eh_top_);
return hasPendingException();
}
const char *
Environment::GetPendingExceptionMessage(const ExceptionHandler *handler)
{
// Note here and elsewhere - this is not a sanity assert. In the future, the
// API may need to query the handler.
assert(handler == eh_top_);
assert(HasPendingException(handler));
return exception_message_;
}
bool
Environment::hasPendingException() const
{
return exception_code_ != SP_ERROR_NONE;
}
void
Environment::clearPendingException()
{
exception_code_ = SP_ERROR_NONE;
}
int
Environment::getPendingExceptionCode() const
{
return exception_code_;
}

View File

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

View File

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

View File

@ -33,6 +33,9 @@ struct HeapTracker
static const size_t SP_MAX_RETURN_STACK = 1024;
class Environment;
class PluginContext;
class PluginContext : public IPluginContext
{
public:
@ -88,6 +91,14 @@ class PluginContext : public IPluginContext
bool GetKey(int k, void **value);
void Refresh();
void ClearLastNativeError();
ISourcePawnEngine2 *APIv2() KE_OVERRIDE;
void ReportError(const char *fmt, ...) KE_OVERRIDE;
void ReportErrorVA(const char *fmt, va_list ap) KE_OVERRIDE;
void ReportFatalError(const char *fmt, ...) KE_OVERRIDE;
void ReportFatalErrorVA(const char *fmt, va_list ap) KE_OVERRIDE;
void ReportErrorNumber(int error) KE_OVERRIDE;
bool Invoke(funcid_t fnid, const cell_t *params, unsigned int num_params, cell_t *result);
size_t HeapSize() const {
return mem_size_;
@ -98,25 +109,16 @@ class PluginContext : public IPluginContext
size_t DataSize() const {
return data_size_;
}
PluginRuntime *runtime() const {
return m_pRuntime;
}
public:
bool IsInExec();
static inline size_t offsetOfRp() {
return offsetof(PluginContext, rp_);
}
static inline size_t offsetOfRstkCips() {
return offsetof(PluginContext, rstk_cips_);
}
static inline size_t offsetOfTracker() {
return offsetof(PluginContext, tracker_);
}
static inline size_t offsetOfLastNative() {
return offsetof(PluginContext, last_native_);
}
static inline size_t offsetOfNativeError() {
return offsetof(PluginContext, native_error_);
}
static inline size_t offsetOfSp() {
return offsetof(PluginContext, sp_);
}
@ -127,9 +129,6 @@ class PluginContext : public IPluginContext
return offsetof(PluginContext, memory_);
}
int32_t *addressOfCip() {
return &cip_;
}
int32_t *addressOfSp() {
return &sp_;
}
@ -140,9 +139,6 @@ class PluginContext : public IPluginContext
return &hp_;
}
int32_t cip() const {
return cip_;
}
cell_t frm() const {
return frm_;
}
@ -150,25 +146,6 @@ class PluginContext : public IPluginContext
return hp_;
}
// Return stack logic.
bool pushReturnCip(cell_t cip) {
if (rp_ >= SP_MAX_RETURN_STACK)
return false;
rstk_cips_[rp_++] = cip;
return true;
}
void popReturnCip() {
assert(rp_ > 0);
rp_--;
}
cell_t rp() const {
return rp_;
}
cell_t getReturnStackCip(int index) {
assert(index >= 0 && index < SP_MAX_RETURN_STACK);
return rstk_cips_[index];
}
int popTrackerAndSetHeap();
int pushTracker(uint32_t amount);
@ -176,9 +153,6 @@ class PluginContext : public IPluginContext
int generateFullArray(uint32_t argc, cell_t *argv, int autozero);
cell_t invokeNative(ucell_t native_idx, cell_t *params);
cell_t invokeBoundNative(SPVM_NATIVE_FUNC pfn, cell_t *params);
int lastNative() const {
return last_native_;
}
inline bool checkAddress(cell_t *stk, cell_t addr) {
if (uint32_t(addr) >= mem_size_)
@ -194,10 +168,7 @@ class PluginContext : public IPluginContext
}
private:
void SetErrorMessage(const char *msg, va_list ap);
void _SetErrorMessage(const char *msg, ...);
private:
Environment *env_;
PluginRuntime *m_pRuntime;
uint8_t *memory_;
uint32_t data_size_;
@ -205,26 +176,12 @@ class PluginContext : public IPluginContext
cell_t *m_pNullVec;
cell_t *m_pNullString;
char m_MsgCache[1024];
bool m_CustomMsg;
bool m_InExec;
void *m_keys[4];
bool m_keys_set[4];
// Tracker for local HEA growth.
HeapTracker tracker_;
// Return stack.
cell_t rp_;
cell_t rstk_cips_[SP_MAX_RETURN_STACK];
// Track the currently executing native index, and any error it throws.
int32_t last_native_;
int native_error_;
// Most recent CIP.
int32_t cip_;
// Stack, heap, and frame pointer.
cell_t sp_;
cell_t hp_;

View File

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

View File

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

View File

@ -15,6 +15,8 @@
#include <string.h>
#include "scripted-invoker.h"
#include "plugin-runtime.h"
#include "environment.h"
#include "plugin-context.h"
/********************
* FUNCTION CALLING *
@ -23,43 +25,14 @@
using namespace sp;
using namespace SourcePawn;
ScriptedInvoker::~ScriptedInvoker()
{
delete [] full_name_;
}
bool
ScriptedInvoker::IsRunnable()
{
return !m_pRuntime->IsPaused();
}
int
ScriptedInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result)
{
return CallFunction2(m_pRuntime->GetDefaultContext(), params, num_params, result);
}
int
ScriptedInvoker::CallFunction2(IPluginContext *pContext, const cell_t *params, unsigned int num_params, cell_t *result)
{
return pContext->Execute2(this, params, num_params, result);
}
IPluginContext *
ScriptedInvoker::GetParentContext()
{
return m_pRuntime->GetDefaultContext();
}
ScriptedInvoker::ScriptedInvoker(PluginRuntime *runtime, funcid_t id, uint32_t pub_id)
: m_curparam(0),
: env_(Environment::get()),
context_(runtime->GetBaseContext()),
m_curparam(0),
m_errorstate(SP_ERROR_NONE),
m_FnId(id),
cc_function_(nullptr)
{
m_pRuntime = runtime;
runtime->GetPublicByIndex(pub_id, &public_);
size_t rt_len = strlen(runtime->Name());
@ -71,6 +44,36 @@ ScriptedInvoker::ScriptedInvoker(PluginRuntime *runtime, funcid_t id, uint32_t p
strcpy(&full_name_[rt_len + 2], public_->name);
}
ScriptedInvoker::~ScriptedInvoker()
{
}
bool
ScriptedInvoker::IsRunnable()
{
return !context_->runtime()->IsPaused();
}
int
ScriptedInvoker::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result)
{
Environment::get()->ReportError(SP_ERROR_ABORTED);
return SP_ERROR_ABORTED;
}
int
ScriptedInvoker::CallFunction2(IPluginContext *pContext, const cell_t *params, unsigned int num_params, cell_t *result)
{
Environment::get()->ReportError(SP_ERROR_ABORTED);
return SP_ERROR_ABORTED;
}
IPluginContext *
ScriptedInvoker::GetParentContext()
{
return context_;
}
int ScriptedInvoker::PushCell(cell_t cell)
{
if (m_curparam >= SP_MAX_EXEC_PARAMS)
@ -169,21 +172,41 @@ ScriptedInvoker::Cancel()
int
ScriptedInvoker::Execute(cell_t *result)
{
return Execute2(m_pRuntime->GetDefaultContext(), result);
Environment *env = Environment::get();
env->clearPendingException();
// For backward compatibility, we have to clear the exception state.
// Otherwise code like this:
//
// static cell_t native(cx, params) {
// for (auto callback : callbacks) {
// callback->Execute();
// }
// }
//
// Could unintentionally leak a pending exception back to the caller,
// which wouldn't have happened before the Great Exception Refactoring.
ExceptionHandler eh(context_);
if (!Invoke(result)) {
assert(env->hasPendingException());
return env->getPendingExceptionCode();
}
int
ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
return SP_ERROR_NONE;
}
bool
ScriptedInvoker::Invoke(cell_t *result)
{
int err = SP_ERROR_NONE;
if (!IsRunnable())
m_errorstate = SP_ERROR_NOT_RUNNABLE;
if (m_errorstate != SP_ERROR_NONE) {
err = m_errorstate;
if (!IsRunnable()) {
Cancel();
return err;
env_->ReportError(SP_ERROR_NOT_RUNNABLE);
return false;
}
if (int err = m_errorstate) {
Cancel();
env_->ReportError(err);
return false;
}
//This is for re-entrancy!
@ -191,7 +214,6 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
ParamInfo temp_info[SP_MAX_EXEC_PARAMS];
unsigned int numparams = m_curparam;
unsigned int i;
bool docopies = true;
if (numparams)
{
@ -201,16 +223,19 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
m_curparam = 0;
/* Browse the parameters and build arrays */
bool ok = true;
for (i=0; 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,
int err = context_->HeapAlloc(
temp_info[i].size,
&(temp_info[i].local_addr),
&(temp_info[i].phys_addr)))
!= SP_ERROR_NONE)
{
&(temp_info[i].phys_addr));
if (err != SP_ERROR_NONE) {
env_->ReportError(err);
ok = false;
break;
}
if (temp_info[i].orig_addr)
@ -222,26 +247,26 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
size_t cells = (temp_info[i].size + sizeof(cell_t) - 1) / sizeof(cell_t);
/* Allocate the buffer */
if ((err=ctx->HeapAlloc(cells,
int err = context_->HeapAlloc(
cells,
&(temp_info[i].local_addr),
&(temp_info[i].phys_addr)))
!= SP_ERROR_NONE)
{
&(temp_info[i].phys_addr));
if (err != SP_ERROR_NONE) {
env_->ReportError(err);
ok = false;
break;
}
/* Copy original string if necessary */
if ((temp_info[i].str.sz_flags & SM_PARAM_STRING_COPY) && (temp_info[i].orig_addr != NULL))
{
/* Cut off UTF-8 properly */
if (temp_info[i].str.sz_flags & SM_PARAM_STRING_UTF8) {
if ((err=ctx->StringToLocalUTF8(temp_info[i].local_addr,
context_->StringToLocalUTF8(
temp_info[i].local_addr,
temp_info[i].size,
(const char *)temp_info[i].orig_addr,
NULL))
!= SP_ERROR_NONE)
{
break;
}
NULL);
}
/* Copy a binary blob */
else if (temp_info[i].str.sz_flags & SM_PARAM_STRING_BINARY)
@ -251,13 +276,10 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
/* Copy ASCII characters */
else
{
if ((err=ctx->StringToLocal(temp_info[i].local_addr,
context_->StringToLocal(
temp_info[i].local_addr,
temp_info[i].size,
(const char *)temp_info[i].orig_addr))
!= SP_ERROR_NONE)
{
break;
}
(const char *)temp_info[i].orig_addr);
}
}
} /* End array/string calculation */
@ -270,14 +292,11 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
}
/* Make the call if we can */
if (err == SP_ERROR_NONE) {
if ((err = CallFunction2(ctx, temp_params, numparams, result)) != SP_ERROR_NONE)
docopies = false;
} else {
docopies = false;
}
if (ok)
ok = context_->Invoke(m_FnId, temp_params, numparams, result);
/* i should be equal to the last valid parameter + 1 */
bool docopies = ok;
while (i--) {
if (!temp_info[i].marked)
continue;
@ -299,17 +318,24 @@ ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
}
}
if ((err=ctx->HeapPop(temp_info[i].local_addr)) != SP_ERROR_NONE)
return err;
if (int err = context_->HeapPop(temp_info[i].local_addr))
env_->ReportError(err);
}
return err;
return !env_->hasPendingException();
}
int
ScriptedInvoker::Execute2(IPluginContext *ctx, cell_t *result)
{
Environment::get()->ReportError(SP_ERROR_ABORTED);
return SP_ERROR_ABORTED;
}
IPluginRuntime *
ScriptedInvoker::GetParentRuntime()
{
return m_pRuntime;
return context_->runtime();
}
funcid_t

View File

@ -14,12 +14,14 @@
#define _INCLUDE_SOURCEMOD_BASEFUNCTION_H_
#include <sp_vm_api.h>
#include <am-utility.h>
namespace sp {
using namespace SourcePawn;
class PluginRuntime;
class PluginContext;
class CompiledFunction;
struct ParamInfo
@ -43,17 +45,18 @@ class ScriptedInvoker : public IPluginFunction
~ScriptedInvoker();
public:
virtual int PushCell(cell_t cell);
virtual int PushCellByRef(cell_t *cell, int flags);
virtual int PushFloat(float number);
virtual int PushFloatByRef(float *number, int flags);
virtual int PushArray(cell_t *inarray, unsigned int cells, int copyback);
virtual int PushString(const char *string);
virtual int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags);
virtual int Execute(cell_t *result);
virtual void Cancel();
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result);
virtual IPluginContext *GetParentContext();
int PushCell(cell_t cell);
int PushCellByRef(cell_t *cell, int flags);
int PushFloat(float number);
int PushFloatByRef(float *number, int flags);
int PushArray(cell_t *inarray, unsigned int cells, int copyback);
int PushString(const char *string);
int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags);
int Execute(cell_t *result);
void Cancel();
int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result);
IPluginContext *GetParentContext();
bool Invoke(cell_t *result);
bool IsRunnable();
funcid_t GetFunctionID();
int Execute2(IPluginContext *ctx, cell_t *result);
@ -83,13 +86,15 @@ class ScriptedInvoker : public IPluginFunction
int SetError(int err);
private:
Environment *env_;
PluginRuntime *m_pRuntime;
PluginContext *context_;
cell_t m_params[SP_MAX_EXEC_PARAMS];
ParamInfo m_info[SP_MAX_EXEC_PARAMS];
unsigned int m_curparam;
int m_errorstate;
funcid_t m_FnId;
char *full_name_;
ke::AutoArray<char> full_name_;
sp_public_t *public_;
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 "x86-utils.h"
#include "jit_x86.h"
#include "environment.h"
using namespace sp;
using namespace SourcePawn;
@ -64,6 +65,12 @@ CodeStubs::CompileInvokeStub()
// Align the stack.
__ andl(esp, 0xfffffff0);
// Set up the last piece of the invoke frame. This lets us find the bounds
// of the call stack.
__ movl(eax, intptr_t(Environment::get()));
__ movl(eax, Operand(eax, Environment::offsetOfTopFrame()));
__ movl(Operand(eax, InvokeFrame::offsetOfEntrySp()), esp);
// Call into plugin (align the stack first).
__ call(ecx);
@ -98,17 +105,11 @@ CodeStubs::CompileInvokeStub()
__ movl(ecx, Operand(ebp, 8 + 4 * 0)); // ret-path expects ecx = ctx
__ jmp(&ret);
Label timeout;
__ bind(&timeout);
__ movl(eax, SP_ERROR_TIMEOUT);
__ jmp(&error);
invoke_stub_ = LinkCode(env_, masm);
if (!invoke_stub_)
return false;
return_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + error.offset();
timeout_stub_ = reinterpret_cast<uint8_t *>(invoke_stub_) + timeout.offset();
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.
__ bind(&jump_map_[cip_ - codeseg]);
// Save the start of the opcode for emitCipMap().
op_cip_ = cip_;
OPCODE op = (OPCODE)readCell();
if (!emitOp(op) || error_ != SP_ERROR_NONE) {
*errp = (error_ == SP_ERROR_NONE) ? SP_ERROR_OUT_OF_MEMORY : error_;
@ -244,6 +247,17 @@ Compiler::emit(int *errp)
}
emitCallThunks();
// For each backward jump, emit a little thunk so we can exit from a timeout.
// Track the offset of where the thunk is, so the watchdog timer can patch it.
for (size_t i = 0; i < backward_jumps_.length(); i++) {
BackwardJump &jump = backward_jumps_[i];
jump.timeout_offset = masm.pc();
__ call(&throw_timeout_);
emitCipMapping(jump.cip);
}
// This has to come last.
emitErrorPaths();
uint8_t *code = LinkCode(env_, masm);
@ -255,44 +269,69 @@ Compiler::emit(int *errp)
AutoPtr<FixedArray<LoopEdge>> edges(
new FixedArray<LoopEdge>(backward_jumps_.length()));
for (size_t i = 0; i < backward_jumps_.length(); i++) {
edges->at(i).offset = backward_jumps_[i];
edges->at(i).disp32 = *reinterpret_cast<int32_t *>(code + edges->at(i).offset - 4);
const BackwardJump &jump = backward_jumps_[i];
edges->at(i).offset = jump.pc;
edges->at(i).disp32 = int32_t(jump.timeout_offset) - int32_t(jump.pc);
}
return new CompiledFunction(code, pcode_start_, edges.take());
AutoPtr<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
InvokePushTracker(PluginContext *cx, uint32_t amount)
{
return cx->pushTracker(amount);
}
// No exit frame - error code is returned directly.
static int
InvokePopTrackerAndSetHeap(PluginContext *cx)
{
return cx->popTrackerAndSetHeap();
}
// Error code must be checked in the environment.
static cell_t
InvokeNativeHelper(PluginContext *cx, ucell_t native_idx, cell_t *params)
{
return cx->invokeNative(native_idx, params);
}
// Error code must be checked in the environment.
static cell_t
InvokeBoundNativeHelper(PluginContext *cx, SPVM_NATIVE_FUNC fn, cell_t *params)
{
return cx->invokeBoundNative(fn, params);
}
// No exit frame - error code is returned directly.
static int
InvokeGenerateFullArray(PluginContext *cx, uint32_t argc, cell_t *argv, int autozero)
{
return cx->generateFullArray(argc, argv, autozero);
}
// Exit frame is a JitExitFrameForHelper.
static void
InvokeReportError(int err)
{
Environment::get()->ReportError(err);
}
// Exit frame is a JitExitFrameForHelper. This is a special function since we
// have to notify the watchdog timer that we're unblocked.
static void
InvokeReportTimeout()
{
Environment::get()->watchdog()->NotifyTimeoutReceived();
InvokeReportError(SP_ERROR_TIMEOUT);
}
bool
Compiler::emitOp(OPCODE op)
{
@ -450,8 +489,16 @@ Compiler::emitOp(OPCODE op)
__ subl(tmp, dat);
__ movl(Operand(frmAddr()), tmp);
// Align the stack to 16-bytes (each call adds 4 bytes).
__ subl(esp, 12);
// Store the function cip for stack traces.
__ push(pcode_start_);
// Align the stack to 16-bytes (each call adds 8 bytes).
__ subl(esp, 8);
#if defined(DEBUG)
// Debug guards.
__ movl(Operand(esp, 0), 0xffaaee00);
__ movl(Operand(esp, 4), 0xffaaee04);
#endif
break;
case OP_IDXADDR_B:
@ -769,14 +816,14 @@ Compiler::emitOp(OPCODE op)
// Guard against divide-by-zero.
__ testl(divisor, divisor);
__ j(zero, &error_divide_by_zero_);
jumpOnError(zero, SP_ERROR_DIVIDE_BY_ZERO);
// A more subtle case; -INT_MIN / -1 yields an overflow exception.
Label ok;
__ cmpl(divisor, -1);
__ j(not_equal, &ok);
__ cmpl(dividend, 0x80000000);
__ j(equal, &error_integer_overflow_);
jumpOnError(equal, SP_ERROR_INTEGER_OVERFLOW);
__ bind(&ok);
// Now we can actually perform the divide.
@ -1078,13 +1125,13 @@ Compiler::emitOp(OPCODE op)
if (amount > 0) {
// Check if the stack went beyond the stack top - usually a compiler error.
__ cmpl(stk, intptr_t(context_->memory() + context_->HeapSize()));
__ j(not_below, &error_stack_min_);
jumpOnError(not_below, SP_ERROR_STACKMIN);
} else {
// Check if the stack is going to collide with the heap.
__ movl(tmp, Operand(hpAddr()));
__ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN));
__ cmpl(stk, tmp);
__ j(below, &error_stack_low_);
jumpOnError(below, SP_ERROR_STACKLOW);
}
break;
}
@ -1097,12 +1144,12 @@ Compiler::emitOp(OPCODE op)
if (amount < 0) {
__ cmpl(Operand(hpAddr()), context_->DataSize());
__ j(below, &error_heap_min_);
jumpOnError(below, SP_ERROR_HEAPMIN);
} else {
__ movl(tmp, Operand(hpAddr()));
__ lea(tmp, Operand(dat, ecx, NoScale, STACK_MARGIN));
__ cmpl(tmp, stk);
__ j(above, &error_heap_low_);
jumpOnError(above, SP_ERROR_HEAPLOW);
}
break;
}
@ -1114,7 +1161,7 @@ Compiler::emitOp(OPCODE op)
return false;
if (target->bound()) {
__ jmp32(target);
backward_jumps_.append(masm.pc());
backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
} else {
__ jmp(target);
}
@ -1131,7 +1178,7 @@ Compiler::emitOp(OPCODE op)
__ testl(pri, pri);
if (target->bound()) {
__ j32(cc, target);
backward_jumps_.append(masm.pc());
backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
} else {
__ j(cc, target);
}
@ -1152,7 +1199,7 @@ Compiler::emitOp(OPCODE op)
__ cmpl(pri, alt);
if (target->bound()) {
__ j32(cc, target);
backward_jumps_.append(masm.pc());
backward_jumps_.append(BackwardJump(masm.pc(), op_cip_));
} else {
__ j(cc, target);
}
@ -1171,7 +1218,7 @@ Compiler::emitOp(OPCODE op)
__ call(ExternalAddress((void *)InvokePushTracker));
__ addl(esp, 8);
__ testl(eax, eax);
__ j(not_zero, &extern_error_);
jumpOnError(not_zero);
__ pop(alt);
__ pop(pri);
@ -1189,31 +1236,32 @@ Compiler::emitOp(OPCODE op)
__ call(ExternalAddress((void *)InvokePopTrackerAndSetHeap));
__ addl(esp, 4);
__ testl(eax, eax);
__ j(not_zero, &extern_error_);
jumpOnError(not_zero);
__ pop(alt);
__ pop(pri);
break;
}
// This opcode is used to note where line breaks occur. We don't support
// live debugging, and if we did, we could build this map from the lines
// table. So we don't generate any code here.
case OP_BREAK:
{
cell_t cip = uintptr_t(cip_ - 1) - uintptr_t(rt_->code().bytes);
__ movl(Operand(cipAddr()), cip);
break;
}
// This should never be hit.
case OP_HALT:
__ align(16);
__ movl(pri, readCell());
__ jmp(&extern_error_);
__ testl(eax, eax);
jumpOnError(not_zero);
break;
case OP_BOUNDS:
{
cell_t value = readCell();
__ cmpl(eax, value);
__ j(above, &error_bounds_);
jumpOnError(above, SP_ERROR_ARRAY_BOUNDS);
break;
}
@ -1281,7 +1329,7 @@ Compiler::emitCheckAddress(Register reg)
{
// Check if we're in memory bounds.
__ cmpl(reg, context_->HeapSize());
__ j(not_below, &error_memaccess_);
jumpOnError(not_below, SP_ERROR_MEMACCESS);
// Check if we're in the invalid region between hp and sp.
Label done;
@ -1289,7 +1337,7 @@ Compiler::emitCheckAddress(Register reg)
__ j(below, &done);
__ lea(tmp, Operand(dat, reg, NoScale));
__ cmpl(tmp, stk);
__ j(below, &error_memaccess_);
jumpOnError(below, SP_ERROR_MEMACCESS);
__ bind(&done);
}
@ -1308,7 +1356,7 @@ Compiler::emitGenArray(bool autozero)
__ movl(Operand(hpAddr()), alt);
__ addl(alt, dat);
__ cmpl(alt, stk);
__ j(not_below, &error_heap_low_);
jumpOnError(not_below, SP_ERROR_HEAPLOW);
__ shll(tmp, 2);
__ push(tmp);
@ -1318,7 +1366,7 @@ Compiler::emitGenArray(bool autozero)
__ pop(tmp);
__ shrl(tmp, 2);
__ testl(eax, eax);
__ j(not_zero, &extern_error_);
jumpOnError(not_zero);
if (autozero) {
// Note - tmp is ecx and still intact.
@ -1347,7 +1395,7 @@ Compiler::emitGenArray(bool autozero)
__ pop(tmp);
__ testl(eax, eax);
__ j(not_zero, &extern_error_);
jumpOnError(not_zero);
// Move tmp back to pri, remove pushed args.
__ movl(pri, tmp);
@ -1367,25 +1415,6 @@ Compiler::emitCall()
return false;
}
// eax = context
// ecx = rp
__ movl(eax, intptr_t(rt_->GetBaseContext()));
__ movl(ecx, Operand(eax, PluginContext::offsetOfRp()));
// Check if the return stack is used up.
__ cmpl(ecx, SP_MAX_RETURN_STACK);
__ j(not_below, &error_stack_low_);
// Add to the return stack.
uintptr_t cip = uintptr_t(cip_ - 2) - uintptr_t(rt_->code().bytes);
__ movl(Operand(eax, ecx, ScaleFour, PluginContext::offsetOfRstkCips()), cip);
// Increment the return stack pointer.
__ addl(Operand(eax, PluginContext::offsetOfRp()), 1);
// Store the CIP of the function we're about to call.
__ movl(Operand(cipAddr()), offset);
CompiledFunction *fun = rt_->GetJittedFunctionByOffset(offset);
if (!fun) {
// Need to emit a delayed thunk.
@ -1398,12 +1427,8 @@ Compiler::emitCall()
__ call(ExternalAddress(fun->GetEntryAddress()));
}
// Restore the last cip.
__ movl(Operand(cipAddr()), cip);
// Mark us as leaving the last frame.
__ movl(tmp, intptr_t(rt_->GetBaseContext()));
__ subl(Operand(tmp, PluginContext::offsetOfRp()), 1);
// Map the return address to the cip that started this call.
emitCipMapping(op_cip_);
return true;
}
@ -1415,15 +1440,27 @@ Compiler::emitCallThunks()
Label error;
__ bind(&thunk->call);
// Huge hack - get the return address, since that is the call that we
// need to patch.
// Get the return address, since that is the call that we need to patch.
__ movl(eax, Operand(esp, 0));
// Push an OP_PROC frame as if we already called the function. This helps
// error reporting.
__ push(thunk->pcode_offset);
__ subl(esp, 8);
// Create the exit frame, then align the stack.
__ push(0);
__ movl(ecx, intptr_t(&Environment::get()->exit_frame()));
__ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1);
__ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp);
// We need to push 4 arguments, and one of them will need an extra word
// on the stack. Allocate a big block so we're aligned, subtracting
// 4 because we got here via a call.
// on the stack. Allocate a big block so we're aligned.
//
// Note: we add 12 since the push above misaligned the stack.
static const size_t kStackNeeded = 5 * sizeof(void *);
static const size_t kStackReserve = ke::Align(kStackNeeded, 16) - sizeof(void *);
static const size_t kStackReserve = ke::Align(kStackNeeded, 16) + 3 * sizeof(void *);
__ subl(esp, kStackReserve);
// Set arguments.
@ -1435,14 +1472,10 @@ Compiler::emitCallThunks()
__ call(ExternalAddress((void *)CompileFromThunk));
__ movl(edx, Operand(esp, 4 * sizeof(void *)));
__ addl(esp, kStackReserve);
__ addl(esp, kStackReserve + 4 * sizeof(void *)); // Drop the exit frame and fake frame.
__ testl(eax, eax);
__ j(not_zero, &error);
jumpOnError(not_zero);
__ jmp(edx);
__ bind(&error);
__ movl(Operand(cipAddr()), thunk->pcode_offset);
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
}
}
@ -1482,18 +1515,23 @@ Compiler::emitNativeCall(OPCODE op)
__ subl(stk, 4);
}
// Create the exit frame. This is a JitExitFrameForNative, so everything we
// push up to the return address of the call instruction is reflected in
// that structure.
__ movl(eax, intptr_t(&Environment::get()->exit_frame()));
__ movl(Operand(eax, ExitFrame::offsetOfExitNative()), native_index);
__ movl(Operand(eax, ExitFrame::offsetOfExitSp()), esp);
// Save registers.
__ push(edx);
// Push the last parameter for the C++ function.
__ push(stk);
__ movl(eax, intptr_t(rt_->GetBaseContext()));
__ movl(Operand(eax, PluginContext::offsetOfLastNative()), native_index);
// Relocate our absolute stk to be dat-relative, and update the context's
// view.
__ subl(stk, dat);
__ movl(eax, intptr_t(context_));
__ movl(Operand(eax, PluginContext::offsetOfSp()), stk);
const sp_native_t *native = rt_->GetNative(native_index);
@ -1512,11 +1550,14 @@ Compiler::emitNativeCall(OPCODE op)
__ call(ExternalAddress((void *)InvokeBoundNativeHelper));
}
// Check for errors.
__ movl(ecx, intptr_t(rt_->GetBaseContext()));
__ movl(ecx, Operand(ecx, PluginContext::offsetOfNativeError()));
__ testl(ecx, ecx);
__ j(not_zero, &extern_error_);
// Map the return address to the cip that initiated this call.
emitCipMapping(op_cip_);
// Check for errors. Note we jump directly to the return stub since the
// error has already been reported.
__ movl(ecx, intptr_t(Environment::get()));
__ cmpl(Operand(ecx, Environment::offsetOfExceptionCode()), 0);
__ j(not_zero, &return_reported_error_);
// Restore local state.
__ addl(stk, dat);
@ -1631,16 +1672,6 @@ Compiler::emitSwitch()
return true;
}
void
Compiler::emitErrorPath(Label *dest, int code)
{
if (dest->used()) {
__ bind(dest);
__ movl(eax, code);
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
}
}
void
Compiler::emitFloatCmp(ConditionCode cc)
{
@ -1691,22 +1722,112 @@ Compiler::emitFloatCmp(ConditionCode cc)
__ addl(stk, 8);
}
void
Compiler::jumpOnError(ConditionCode cc, int err)
{
// Note: we accept 0 for err. In this case we expect the error to be in eax.
{
ErrorPath path(op_cip_, err);
error_paths_.append(path);
}
ErrorPath &path = error_paths_.back();
__ j(cc, &path.label);
}
void
Compiler::emitErrorPaths()
{
emitErrorPath(&error_divide_by_zero_, SP_ERROR_DIVIDE_BY_ZERO);
emitErrorPath(&error_stack_low_, SP_ERROR_STACKLOW);
emitErrorPath(&error_stack_min_, SP_ERROR_STACKMIN);
emitErrorPath(&error_bounds_, SP_ERROR_ARRAY_BOUNDS);
emitErrorPath(&error_memaccess_, SP_ERROR_MEMACCESS);
emitErrorPath(&error_heap_low_, SP_ERROR_HEAPLOW);
emitErrorPath(&error_heap_min_, SP_ERROR_HEAPMIN);
emitErrorPath(&error_integer_overflow_, SP_ERROR_INTEGER_OVERFLOW);
// For each path that had an error check, bind it to an error routine and
// add it to the cip map. What we'll get is something like:
//
// cmp dividend, 0
// jz error_thunk_0
//
// error_thunk_0:
// call integer_overflow
//
// integer_overflow:
// mov eax, SP_ERROR_DIVIDE_BY_ZERO
// jmp report_error
//
// report_error:
// create exit frame
// push eax
// call InvokeReportError(int err)
//
for (size_t i = 0; i < error_paths_.length(); i++) {
ErrorPath &path = error_paths_[i];
if (extern_error_.used()) {
__ bind(&extern_error_);
__ movl(eax, intptr_t(rt_->GetBaseContext()));
__ movl(eax, Operand(eax, PluginContext::offsetOfNativeError()));
// If there's no error code, it should be in eax. Otherwise we'll jump to
// a path that sets eax to a hardcoded value.
__ bind(&path.label);
if (path.err == 0)
__ call(&report_error_);
else
__ call(&throw_error_code_[path.err]);
emitCipMapping(path.cip);
}
emitThrowPathIfNeeded(SP_ERROR_DIVIDE_BY_ZERO);
emitThrowPathIfNeeded(SP_ERROR_STACKLOW);
emitThrowPathIfNeeded(SP_ERROR_STACKMIN);
emitThrowPathIfNeeded(SP_ERROR_ARRAY_BOUNDS);
emitThrowPathIfNeeded(SP_ERROR_MEMACCESS);
emitThrowPathIfNeeded(SP_ERROR_HEAPLOW);
emitThrowPathIfNeeded(SP_ERROR_HEAPMIN);
emitThrowPathIfNeeded(SP_ERROR_INTEGER_OVERFLOW);
if (report_error_.used()) {
__ bind(&report_error_);
// Create the exit frame. We always get here through a call from the opcode
// (and always via an out-of-line thunk).
__ movl(ecx, intptr_t(&Environment::get()->exit_frame()));
__ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1);
__ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp);
// Since the return stub wipes out the stack, we don't need to subl after
// the call.
__ push(eax);
__ call(ExternalAddress((void *)InvokeReportError));
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
}
// We get here if we know an exception is already pending.
if (return_reported_error_.used()) {
__ bind(&return_reported_error_);
__ movl(eax, intptr_t(Environment::get()));
__ movl(eax, Operand(eax, Environment::offsetOfExceptionCode()));
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
}
// The timeout uses a special stub.
if (throw_timeout_.used()) {
__ bind(&throw_timeout_);
// Create the exit frame.
__ movl(ecx, intptr_t(&Environment::get()->exit_frame()));
__ movl(Operand(ecx, ExitFrame::offsetOfExitNative()), -1);
__ movl(Operand(ecx, ExitFrame::offsetOfExitSp()), esp);
// Since the return stub wipes out the stack, we don't need to subl after
// the call.
__ call(ExternalAddress((void *)InvokeReportTimeout));
__ jmp(ExternalAddress(env_->stubs()->ReturnStub()));
}
}
void
Compiler::emitThrowPathIfNeeded(int err)
{
assert(err < SP_MAX_ERROR_CODES);
if (!throw_error_code_[err].used())
return;
__ bind(&throw_error_code_[err]);
__ movl(eax, err);
__ jmp(&report_error_);
}

View File

@ -33,6 +33,36 @@ class LegacyImage;
class Environment;
class CompiledFunction;
struct ErrorPath
{
SilentLabel label;
const cell_t *cip;
int err;
ErrorPath(const cell_t *cip, int err)
: cip(cip),
err(err)
{}
ErrorPath()
{}
};
struct BackwardJump {
// The pc at the jump instruction (i.e. after it).
uint32_t pc;
// The cip of the jump.
const cell_t *cip;
// The offset of the timeout thunk. This is filled in at the end.
uint32_t timeout_offset;
BackwardJump()
{}
BackwardJump(uint32_t pc, const cell_t *cip)
: pc(pc),
cip(cip)
{}
};
#define JIT_INLINE_ERRORCHECKS (1<<0)
#define JIT_INLINE_NATIVES (1<<1)
#define STACK_MARGIN 64 //8 parameters of safety, I guess
@ -42,21 +72,9 @@ class CompiledFunction;
#define sDIMEN_MAX 5 //this must mirror what the compiler has.
typedef struct funcinfo_s
{
unsigned int magic;
unsigned int index;
} funcinfo_t;
typedef struct functracker_s
{
unsigned int num_functions;
unsigned int code_size;
} functracker_t;
struct CallThunk
{
Label call;
SilentLabel call;
cell_t pcode_offset;
CallThunk(cell_t pcode_offset)
@ -89,10 +107,9 @@ class Compiler
void emitErrorPath(Label *dest, int code);
void emitErrorPaths();
void emitFloatCmp(ConditionCode cc);
void jumpOnError(ConditionCode cc, int err = 0);
void emitThrowPathIfNeeded(int err);
ExternalAddress cipAddr() {
return ExternalAddress(context_->addressOfCip());
}
ExternalAddress hpAddr() {
return ExternalAddress(context_->addressOfHp());
}
@ -100,6 +117,16 @@ class Compiler
return ExternalAddress(context_->addressOfFrm());
}
// Map a return address (i.e. an exit point from a function) to its source
// cip. This lets us avoid tracking the cip during runtime. These are
// sorted by definition since we assemble and emit in forward order.
void emitCipMapping(const cell_t *cip) {
CipMapEntry entry;
entry.cipoffs = uintptr_t(cip) - uintptr_t(code_start_);
entry.pcoffs = masm.pc();
cip_map_.append(entry);
}
private:
AssemblerX86 masm;
Environment *env_;
@ -110,20 +137,19 @@ class Compiler
uint32_t pcode_start_;
const cell_t *code_start_;
const cell_t *cip_;
const cell_t *op_cip_;
const cell_t *code_end_;
Label *jump_map_;
ke::Vector<size_t> backward_jumps_;
ke::Vector<BackwardJump> backward_jumps_;
// Errors
Label error_bounds_;
Label error_heap_low_;
Label error_heap_min_;
Label error_stack_low_;
Label error_stack_min_;
Label error_divide_by_zero_;
Label error_memaccess_;
Label error_integer_overflow_;
Label extern_error_;
ke::Vector<CipMapEntry> cip_map_;
// Errors.
ke::Vector<ErrorPath> error_paths_;
Label throw_timeout_;
Label throw_error_code_[SP_MAX_ERROR_CODES];
Label report_error_;
Label return_reported_error_;
ke::Vector<CallThunk *> thunks_; //:TODO: free
};