diff --git a/PyV8.py b/PyV8.py index e814e38..de025e0 100644 --- a/PyV8.py +++ b/PyV8.py @@ -115,7 +115,13 @@ def int_or_nul(value): def frames(self): return self.parse_stack(self.stackTrace) + +class JSTimeoutError(Exception): + pass + + _PyV8._JSError._jsclass = JSError +_PyV8._JSTimeoutError._jsclass = JSTimeoutError JSObject = _PyV8.JSObject JSNull = _PyV8.JSNull diff --git a/src/Context.cpp b/src/Context.cpp index 9c515f3..b24e0e4 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -50,12 +50,14 @@ void CContext::Expose(void) py::arg("name") = std::string(), py::arg("line") = -1, py::arg("col") = -1, - py::arg("precompiled") = py::object())) + py::arg("precompiled") = py::object(), + py::arg("timeout") = py::long_(0))) .def("eval", &CContext::EvaluateW, (py::arg("source"), py::arg("name") = std::wstring(), py::arg("line") = -1, py::arg("col") = -1, - py::arg("precompiled") = py::object())) + py::arg("precompiled") = py::object(), + py::arg("timeout") = py::long_(0))) .def("enter", &CContext::Enter, "Enter this context. " "After entering a context, all code compiled and " @@ -205,26 +207,78 @@ py::object CContext::GetCalling(void) py::object(py::handle<>(boost::python::converter::shared_ptr_to_python(CContextPtr(new CContext(calling))))); } +static bool EvaluationTimeout = false; + +void TerminateEvaluation(int sig) +{ + EvaluationTimeout = true; + v8::V8::TerminateExecution(v8::Isolate::GetCurrent()); +} + +inline void SetupEvaluationAlarmHook(long timeout) +{ + EvaluationTimeout = false; + signal(SIGALRM, TerminateEvaluation); + ualarm((useconds_t)(timeout * 1000), (useconds_t)0); +} + +inline void CheckEvaluationAlarm() +{ + signal(SIGALRM, SIG_IGN); + ualarm((useconds_t)0, (useconds_t)0); + + if (EvaluationTimeout) + { + EvaluationTimeout = false; + + throw CJavascriptTimeoutException("Javascript evaluation timeout"); + } +} + py::object CContext::Evaluate(const std::string& src, const std::string name, int line, int col, - py::object precompiled) + py::object precompiled, long timeout) { CEngine engine(v8::Isolate::GetCurrent()); CScriptPtr script = engine.Compile(src, name, line, col, precompiled); - return script->Run(); + if (timeout > 0) + { + SetupEvaluationAlarmHook(timeout); + } + + py::object result = script->Run(); + + if (timeout > 0) + { + CheckEvaluationAlarm(); + } + + return result; } py::object CContext::EvaluateW(const std::wstring& src, const std::wstring name, int line, int col, - py::object precompiled) + py::object precompiled, long timeout) { CEngine engine(v8::Isolate::GetCurrent()); CScriptPtr script = engine.CompileW(src, name, line, col, precompiled); - return script->Run(); + if (timeout > 0) + { + SetupEvaluationAlarmHook(timeout); + } + + py::object result = script->Run(); + + if (timeout > 0) + { + CheckEvaluationAlarm(); + } + + return result; } diff --git a/src/Context.h b/src/Context.h index 8603ae1..824e7e1 100644 --- a/src/Context.h +++ b/src/Context.h @@ -66,9 +66,9 @@ class CContext bool HasOutOfMemoryException(void) { v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); return Handle()->HasOutOfMemoryException(); } py::object Evaluate(const std::string& src, const std::string name = std::string(), - int line = -1, int col = -1, py::object precompiled = py::object()); + int line = -1, int col = -1, py::object precompiled = py::object(), long timeout = 0); py::object EvaluateW(const std::wstring& src, const std::wstring name = std::wstring(), - int line = -1, int col = -1, py::object precompiled = py::object()); + int line = -1, int col = -1, py::object precompiled = py::object(), long timeout = 0); static py::object GetEntered(void); static py::object GetCurrent(void); diff --git a/src/Exception.cpp b/src/Exception.cpp index 5944052..c7a6b51 100644 --- a/src/Exception.cpp +++ b/src/Exception.cpp @@ -76,6 +76,13 @@ void CJavascriptException::Expose(void) ExceptionTranslator::Construct, py::type_id()); } +void CJavascriptTimeoutException::Expose(void) +{ + py::class_("_JSTimeoutError", py::no_init); + + py::register_exception_translator(ExceptionTranslator::TranslateTimeout); +} + CJavascriptStackTracePtr CJavascriptStackTrace::GetCurrentStackTrace( v8::Isolate *isolate, int frame_limit, v8::StackTrace::StackTraceOptions options) { @@ -392,6 +399,22 @@ void ExceptionTranslator::Translate(CJavascriptException const& ex) } } +void ExceptionTranslator::TranslateTimeout(CJavascriptTimeoutException const& ex) +{ + CPythonGIL python_gil; + + // Boost::Python doesn't support inherite from Python class, + // so, just use some workaround to throw our custom exception + // + // http://www.language-binding.net/pyplusplus/troubleshooting_guide/exceptions/exceptions.html + + py::object impl(ex); + py::object clazz = impl.attr("_jsclass"); + py::object err = clazz(impl); + + ::PyErr_SetObject(clazz.ptr(), py::incref(err.ptr())); +} + void *ExceptionTranslator::Convertible(PyObject* obj) { CPythonGIL python_gil; diff --git a/src/Exception.h b/src/Exception.h index d0c9481..2b0a676 100644 --- a/src/Exception.h +++ b/src/Exception.h @@ -19,10 +19,12 @@ #define END_HANDLE_JAVASCRIPT_EXCEPTION if (try_catch.HasCaught()) CJavascriptException::ThrowIf(v8::Isolate::GetCurrent(), try_catch); class CJavascriptException; +class CJavascriptTimeoutException; struct ExceptionTranslator { static void Translate(CJavascriptException const& ex); + static void TranslateTimeout(CJavascriptTimeoutException const& ex); static void *Convertible(PyObject* obj); static void Construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data); @@ -178,3 +180,21 @@ class CJavascriptException : public std::runtime_error static void Expose(void); }; + + +class CJavascriptTimeoutException : public std::runtime_error +{ +public: + CJavascriptTimeoutException(const std::string& msg) : + std::runtime_error(msg) + { + } + + CJavascriptTimeoutException(const CJavascriptTimeoutException& ex) + : std::runtime_error(ex.what()) + { + } + + static void Expose(void); +}; + diff --git a/src/PyV8.cpp b/src/PyV8.cpp index a5b15c6..b8665e2 100644 --- a/src/PyV8.cpp +++ b/src/PyV8.cpp @@ -12,6 +12,7 @@ BOOST_PYTHON_MODULE(_PyV8) { CJavascriptException::Expose(); + CJavascriptTimeoutException::Expose(); CWrapper::Expose(); CContext::Expose(); #ifdef SUPPORT_AST diff --git a/src/Wrapper.cpp b/src/Wrapper.cpp index e3d318e..f67e2cf 100644 --- a/src/Wrapper.cpp +++ b/src/Wrapper.cpp @@ -15,6 +15,9 @@ #include "Context.h" #include "Utils.h" +#include +#include + #define TERMINATE_EXECUTION_CHECK(returnValue) \ if(v8::V8::IsExecutionTerminating()) { \ ::PyErr_Clear(); \ @@ -112,12 +115,14 @@ void CWrapper::Expose(void) .def("apply", &CJavascriptFunction::ApplyPython, (py::arg("self"), py::arg("args") = py::list(), - py::arg("kwds") = py::dict()), + py::arg("kwds") = py::dict(), + py::arg("timeout") = py::long_(0)), "Performs a function call using the parameters.") .def("invoke", &CJavascriptFunction::Invoke, (py::arg("args") = py::list(), - py::arg("kwds") = py::dict()), - "Performs a binding method call using the parameters.") + py::arg("kwds") = py::dict(), + py::arg("timeout") = py::long_(0)), + "Performs a binding method call using the parameters.") .def("setName", &CJavascriptFunction::SetName) @@ -1656,22 +1661,78 @@ py::object CJavascriptFunction::CreateWithArgs(CJavascriptFunctionPtr proto, py: return CJavascriptObject::Wrap(result); } -py::object CJavascriptFunction::ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds) +static bool ApplyJavascriptTimeout = false; + +void TerminateExecution(int sig) +{ + ApplyJavascriptTimeout = true; + v8::V8::TerminateExecution(v8::Isolate::GetCurrent()); +} + +inline void SetupAlarmHook(long timeout) +{ + ApplyJavascriptTimeout = false; + signal(SIGALRM, TerminateExecution); + ualarm((useconds_t)(timeout * 1000), (useconds_t)0); +} + +inline void CheckAlarm() +{ + signal(SIGALRM, SIG_IGN); + ualarm((useconds_t)0, (useconds_t)0); + + if (ApplyJavascriptTimeout) + { + ApplyJavascriptTimeout = false; + + throw CJavascriptTimeoutException("Javascript function call timeout"); + } +} + +py::object CJavascriptFunction::ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds, py::long_ timeout) { CHECK_V8_CONTEXT(); v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); - - return Call(self->Object(), args, kwds); + + long timeout_ = py::extract(timeout); + + if (timeout_ > 0) + { + SetupAlarmHook(timeout_); + } + + py::object result = Call(self->Object(), args, kwds); + + if (timeout_ > 0) + { + CheckAlarm(); + } + + return result; } -py::object CJavascriptFunction::ApplyPython(py::object self, py::list args, py::dict kwds) +py::object CJavascriptFunction::ApplyPython(py::object self, py::list args, py::dict kwds, py::long_ timeout) { CHECK_V8_CONTEXT(); v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); + + long timeout_ = py::extract(timeout); + + if (timeout_ > 0) + { + SetupAlarmHook(timeout_); + } - return Call(CPythonObject::Wrap(self)->ToObject(), args, kwds); + py::object result = Call(CPythonObject::Wrap(self)->ToObject(), args, kwds); + + if (timeout_ > 0) + { + CheckAlarm(); + } + + return result; } py::object CJavascriptFunction::Invoke(py::list args, py::dict kwds) diff --git a/src/Wrapper.h b/src/Wrapper.h index 646371d..8ccfe9d 100644 --- a/src/Wrapper.h +++ b/src/Wrapper.h @@ -191,8 +191,8 @@ class CJavascriptFunction : public CJavascriptObject static py::object CallWithArgs(py::tuple args, py::dict kwds); static py::object CreateWithArgs(CJavascriptFunctionPtr proto, py::tuple args, py::dict kwds); - py::object ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds); - py::object ApplyPython(py::object self, py::list args, py::dict kwds); + py::object ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds, py::long_ timeout); + py::object ApplyPython(py::object self, py::list args, py::dict kwds, py::long_ timeout); py::object Invoke(py::list args, py::dict kwds); const std::string GetName(void) const;