From da1cd9eb11f1cd2c9e7063069a98fe6c64fe7fc3 Mon Sep 17 00:00:00 2001
From: BotoX <botox@botox.bz>
Date: Mon, 16 Jan 2017 14:29:14 +0100
Subject: [PATCH] Extend function calling API for natives and allow catching
 exceptions. Change sourcepawn url.

---
 .gitmodules                   |   2 +-
 core/logic/AMBuilder          |   2 +
 core/logic/NativeInvoker.cpp  | 321 ++++++++++++++++++++++++++++++++++
 core/logic/NativeInvoker.h    |  79 +++++++++
 core/logic/smn_functions.cpp  |  62 +++++++
 plugins/include/functions.inc |  26 ++-
 sourcepawn                    |   2 +-
 7 files changed, 491 insertions(+), 3 deletions(-)
 create mode 100644 core/logic/NativeInvoker.cpp
 create mode 100644 core/logic/NativeInvoker.h

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