From ac878007602a8fb06d17de5daa559a31fabf85cb Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Tue, 27 Jul 2004 03:57:31 +0000 Subject: Major re-jig. With thanks to Rob Richards for tracking down a couple of big bugs caused by teeny bits of code. --- sapi/activescript/scriptengine.cpp | 1755 ++++++++++++++++++------------------ 1 file changed, 882 insertions(+), 873 deletions(-) (limited to 'sapi/activescript/scriptengine.cpp') diff --git a/sapi/activescript/scriptengine.cpp b/sapi/activescript/scriptengine.cpp index 9906ca1490..56e5c797c5 100644 --- a/sapi/activescript/scriptengine.cpp +++ b/sapi/activescript/scriptengine.cpp @@ -45,6 +45,7 @@ extern "C" { #include "php5activescript.h" #include "ext/com_dotnet/php_com_dotnet.h" #include "ext/com_dotnet/php_com_dotnet_internal.h" +#include "zend_exceptions.h" } #include "php_ticks.h" #include "php5as_scriptengine.h" @@ -52,7 +53,30 @@ extern "C" { #include #undef php_win_err -#define ACTIVEPHP_THREADING_MODE COINIT_MULTITHREADED +static int clone_frags(void *pDest, void *arg TSRMLS_DC); + +#define ENGINE_THREAD_ONLY(type, method) \ + if (tsrm_thread_id() != m_enginethread) { \ + trace("WRONG THREAD !! " #type "::" #method "\n"); \ + return RPC_E_WRONG_THREAD; \ + } \ + trace("[direct] " #type "::" #method "\n"); + + +#define ASS_CALL(ret, method, args) \ + if (tsrm_thread_id() == m_basethread) { \ + trace("Calling [direct] m_pass->" #method "\n"); \ + ret = m_pass->method args; \ + } else { \ + IActiveScriptSite *ass; \ + trace("Calling [marshall] m_pass->" #method "\n"); \ + ret = GIT_get(m_asscookie, IID_IActiveScriptSite, (void**)&ass); \ + if (SUCCEEDED(ret)) { \ + ret = ass->method args; \ + ass->Release(); \ + } \ + } \ + trace("--- done calling m_pass->" #method "\n"); /* {{{ trace */ static inline void trace(char *fmt, ...) @@ -99,10 +123,12 @@ class TWideString { TWideString(LPOLESTR olestr) { m_ole = olestr; m_ansi = NULL; + m_ansi_strlen = 0; } TWideString(LPCOLESTR olestr) { m_ole = (LPOLESTR)olestr; m_ansi = NULL; + m_ansi_strlen = 0; } ~TWideString() { @@ -110,6 +136,7 @@ class TWideString { CoTaskMemFree(m_ansi); } m_ansi = NULL; + m_ansi_strlen = 0; } char *safe_ansi_string() { @@ -162,12 +189,13 @@ class TWideString { if (m_ansi_strlen) { m_ansi_strlen--; - m_ansi[m_ansi_strlen] = 0; - } else { trace("conversion failed with return code %08x\n", GetLastError()); } } + if (m_ansi) { + m_ansi[m_ansi_strlen] = 0; + } } return m_ansi; } @@ -213,16 +241,6 @@ static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engi /* }}} */ -/* Magic for handling threading correctly */ -static inline HRESULT SEND_THREAD_MESSAGE(TPHPScriptingEngine *engine, LONG msg, WPARAM wparam, LPARAM lparam TSRMLS_DC) -{ - if (engine->m_enginethread == 0) - return E_UNEXPECTED; - if (tsrm_thread_id() == (engine)->m_enginethread) - return (engine)->engine_thread_handler((msg), (wparam), (lparam), NULL TSRMLS_CC); - return (engine)->SendThreadMessage((msg), (wparam), (lparam)); -} - /* These functions do some magic so that interfaces can be * used across threads without worrying about marshalling * or not marshalling, as appropriate. @@ -353,19 +371,183 @@ public: } }; /* }}} */ + STDMETHODIMP TPHPScriptingEngine::GetTypeInfoCount(unsigned int * pctinfo) { + *pctinfo = 0; + trace("%08x: ScriptDispatch: GetTypeInfoCount\n", this); + return S_OK; + } + STDMETHODIMP TPHPScriptingEngine::GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo **ppTInfo) { + trace("%08x: ScriptDispatch: GetTypeInfo\n", this); + return DISP_E_BADINDEX; + } -/* {{{ This object represents the PHP engine to the scripting host. - * Although the docs say it's implementation is optional, I found that - * the Windows Script host would crash if we did not provide it. */ -class ScriptDispatch: - public IDispatchImpl +int TPHPScriptingEngine::create_id(OLECHAR *name, DISPID *dispid TSRMLS_DC) { -public: - ScriptDispatch() { - m_refcount = 1; + int ex = 0; + char *lcname; + int l; + + if (m_ids >= 1023) { + trace("too many ids\n"); + return 0; } -}; -/* }}} */ + + TWideString aname(name); + + l = aname.ansi_len(); + lcname = zend_str_tolower_dup(aname.ansi_string(), l); + ex = zend_hash_exists(EG(function_table), lcname, l+1); + efree(lcname); + + if (!ex) { + trace("no such id %s\n", aname.ansi_string()); + return 0; + } + + /* do we already have an id for this name? */ + int i; + for (i = 0; i < m_ids; i++) { + if (!strcasecmp(m_names[i], aname.ansi_string())) { + trace("already had this ID\n"); + return i; + } + } + + m_lens[m_ids] = aname.ansi_len(); + m_names[m_ids] = aname.ansi_string(); + trace("created ID %d for name %s\n", m_ids, m_names[m_ids]); + aname.m_ansi = NULL; + *dispid = m_ids; + m_ids++; + return 1; +} + +STDMETHODIMP TPHPScriptingEngine::GetIDsOfNames( REFIID riid, OLECHAR **rgszNames, unsigned int cNames, LCID lcid, DISPID *rgDispId) +{ + unsigned int i; + HRESULT ret = S_OK; + TSRMLS_FETCH(); + + if (tsrm_thread_id() != m_enginethread) { + trace("GetIDsOfNames called from wrong thread\n"); + return RPC_E_WRONG_THREAD; + } + + trace("%08x: ScriptDispatch: GetIDsOfNames %d names: \n", this, cNames); + for (i = 0; i < cNames; i++) { + if (!create_id(rgszNames[i], &rgDispId[i] TSRMLS_CC)) { + ret = DISP_E_UNKNOWNNAME; + break; + } + } + return ret; +} + +STDMETHODIMP TPHPScriptingEngine::Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS FAR* pdp, VARIANT FAR* pvarRes, EXCEPINFO FAR* pei, + unsigned int FAR* puArgErr) +{ + UINT i; + zval *retval = NULL; + zval ***params = NULL; + HRESULT ret = DISP_E_MEMBERNOTFOUND; + char *name; + int namelen; + TSRMLS_FETCH(); + + if (tsrm_thread_id() != m_enginethread) { + trace("Invoke called from wrong thread\n"); + return RPC_E_WRONG_THREAD; + } + + name = m_names[dispIdMember]; + namelen = m_lens[dispIdMember]; + + trace("%08x: ScriptDispatch: Invoke dispid %08x [%s]\n", this, dispIdMember, name); + /* this code is similar to that of our com_wrapper InvokeEx implementation */ + + /* convert args into zvals. + * Args are in reverse order */ + if (pdp->cArgs) { + params = (zval ***)safe_emalloc(sizeof(zval **), pdp->cArgs, 0); + for (i = 0; i < pdp->cArgs; i++) { + VARIANT *arg; + zval *zarg; + + arg = &pdp->rgvarg[ pdp->cArgs - 1 - i]; + + trace("alloc zval for arg %d VT=%08x\n", i, V_VT(arg)); + + ALLOC_INIT_ZVAL(zarg); + php_com_wrap_variant(zarg, arg, CP_ACP TSRMLS_CC); + params[i] = &zarg; + } + } + + trace("arguments processed, prepare to do some work\n"); + + /* TODO: if PHP raises an exception here, we should catch it + * and expose it as a COM exception */ + +#if 0 + if (wFlags & DISPATCH_PROPERTYGET) { + retval = zend_read_property(Z_OBJCE_P(disp->object), disp->object, m_names[dispIdMember], m_lens[dispIdMember]+1, 1 TSRMLS_CC); + } else if (wFlags & DISPATCH_PROPERTYPUT) { + zend_update_property(Z_OBJCE_P(disp->object), disp->object, m_names[dispIdMember], m_lens[dispIdMember]+1, *params[0] TSRMLS_CC); + } else +#endif + if (wFlags & DISPATCH_METHOD) { + zval *zname; + MAKE_STD_ZVAL(zname); + ZVAL_STRINGL(zname, (char*)name, namelen, 1); + trace("invoke function %s\n", Z_STRVAL_P(zname)); + + zend_try { + + if (SUCCESS == call_user_function_ex(CG(function_table), NULL, zname, + &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) { + ret = S_OK; + trace("we ran it\n"); + } else { + ret = DISP_E_EXCEPTION; + trace("no such function\n"); + } + + } zend_catch { + ret = DISP_E_EXCEPTION; + /* need to populate the exception here */ + trace("bork\n"); + } zend_end_try(); + + zval_ptr_dtor(&zname); + } else { + trace("Don't know how to handle this invocation %08x\n", wFlags); + ret = E_UNEXPECTED; + } + + /* release arguments */ + for (i = 0; i < pdp->cArgs; i++) + zval_ptr_dtor(params[i]); + + if (params) + efree(params); + + /* return value */ + if (retval) { + if (pvarRes) { + VariantInit(pvarRes); + trace("setting up return value\n"); + php_com_variant_from_zval(pvarRes, retval, CP_ACP TSRMLS_CC); + } + zval_ptr_dtor(&retval); + } else if (pvarRes) { + VariantInit(pvarRes); + } + + trace("Invocation complete\n"); + + return ret; +} /* {{{ This object is used in conjunction with IActiveScriptParseProcedure to * allow scriptlets to be bound to events. IE uses this for declaring @@ -390,7 +572,7 @@ public: if (m_frag) { trace("%08x: Procedure Dispatch: Invoke dispid %08x\n", this, dispIdMember); - SEND_THREAD_MESSAGE(m_engine, PHPSE_EXEC_PROC, 0, (LPARAM)this TSRMLS_CC); + execute_code_fragment(m_frag, NULL, NULL TSRMLS_CC); } return S_OK; } @@ -496,10 +678,16 @@ static void free_code_fragment(code_frag *frag TSRMLS_DC) break; } - if (frag->opcodes) + if (frag->opcodes) { destroy_op_array(frag->opcodes TSRMLS_CC); - if (frag->functionname) + frag->opcodes = NULL; + } + + if (frag->functionname) { CoTaskMemFree(frag->functionname); + frag->functionname = NULL; + } + CoTaskMemFree(frag->code); CoTaskMemFree(frag); } @@ -533,23 +721,12 @@ trace("%08x: CLONED FRAG\n", newfrag); pv.value.str.val = newfrag->code; pv.value.str.len = newfrag->codelen; - newfrag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC); + /* we defer compilation until we are ready to execute, + * as we need the host to AddNamedItem certain autoglobals + * BEFORE we compile */ + newfrag->opcodes = NULL; - if (newfrag->opcodes == NULL) { - free_code_fragment(newfrag TSRMLS_CC); -/* - if (excepinfo) { - memset(excepinfo, 0, sizeof(EXCEPINFO)); - excepinfo->wCode = 1000; - excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment"); - excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling"); - } -*/ - return NULL; - } - return newfrag; - } static int execute_code_fragment(code_frag *frag, @@ -558,16 +735,26 @@ static int execute_code_fragment(code_frag *frag, TSRMLS_DC) { zval *retval_ptr = NULL; - jmp_buf *orig_jmpbuf; - jmp_buf err_trap; if (frag->fragtype == FRAG_MAIN && frag->executed) return 1; - - orig_jmpbuf = frag->engine->m_err_trap; - frag->engine->m_err_trap = &err_trap; - if (setjmp(err_trap) == 0) { + /* compiled cloned fragment, JIT */ + if (frag->persistent && frag->opcodes == NULL) { + zval pv; + pv.type = IS_STRING; + pv.value.str.val = frag->code; + pv.value.str.len = frag->codelen; + + frag->opcodes = compile_string(&pv, "fragment (JIT)" TSRMLS_CC); + + if (!frag->opcodes) { + trace("*** JIT compilation of cloned opcodes failed??"); + return 0; + } + } + + zend_try { trace("*** Executing code in thread %08x\n", tsrm_thread_id()); if (frag->functionname) { @@ -581,30 +768,29 @@ static int execute_code_fragment(code_frag *frag, call_user_function_ex(CG(function_table), NULL, &fname, &retval_ptr, 0, NULL, 1, NULL TSRMLS_CC); } else { - zend_op_array *active_op_array = EG(active_op_array); - zend_function_state *function_state_ptr = EG(function_state_ptr); - zval **return_value_ptr_ptr = EG(return_value_ptr_ptr); - zend_op **opline_ptr = EG(opline_ptr); - - EG(return_value_ptr_ptr) = &retval_ptr; - EG(active_op_array) = frag->opcodes; - EG(no_extensions) = 1; - - zend_execute(frag->opcodes TSRMLS_CC); - - EG(no_extensions) = 0; - EG(opline_ptr) = opline_ptr; - EG(active_op_array) = active_op_array; - EG(function_state_ptr) = function_state_ptr; - EG(return_value_ptr_ptr) = return_value_ptr_ptr; + zend_fcall_info_cache fci_cache; + zend_fcall_info fci; + + memset(&fci, 0, sizeof(fci)); + memset(&fci_cache, 0, sizeof(fci_cache)); + + fci.size = sizeof(fci); + fci.function_table = CG(function_table); + fci.retval_ptr_ptr = &retval_ptr; + fci.no_separation = 1; + + fci_cache.initialized = 1; + fci_cache.function_handler = (zend_function*)frag->opcodes; + frag->opcodes->type = ZEND_USER_FUNCTION; // mini hack + + zend_call_function(&fci, &fci_cache TSRMLS_CC); + } - } else { + } zend_catch { trace("*** --> caught error while executing\n"); if (frag->engine->m_in_main) frag->engine->m_stop_main = 1; - } - - frag->engine->m_err_trap = orig_jmpbuf; + } zend_end_try(); if (frag->fragtype == FRAG_MAIN) frag->executed = 1; @@ -628,694 +814,231 @@ static void frag_dtor(void *pDest) free_code_fragment(frag TSRMLS_CC); } /* }}} */ - -/* glue for getting back into the OO land */ -static DWORD WINAPI begin_engine_thread(LPVOID param) -{ - TPHPScriptingEngine *engine = (TPHPScriptingEngine*)param; - engine->engine_thread_func(); - trace("engine thread has really gone away!\n"); - return 0; -} - -TPHPScriptingEngine::TPHPScriptingEngine() -{ - m_scriptstate = SCRIPTSTATE_UNINITIALIZED; - m_pass = NULL; - m_in_main = 0; - m_stop_main = 0; - m_err_trap = NULL; - m_lambda_count = 0; - m_pass_eng = NULL; - m_refcount = 1; - m_basethread = tsrm_thread_id(); - m_mutex = tsrm_mutex_alloc(); - m_sync_thread_msg = CreateEvent(NULL, TRUE, FALSE, NULL); - TPHPClassFactory::AddToObjectCount(); - m_engine_thread_handle = CreateThread(NULL, 0, begin_engine_thread, this, 0, &m_enginethread); -} - -void activescript_run_ticks(int count) +void TPHPScriptingEngine::do_clone(TPHPScriptingEngine *src) { - MSG msg; TSRMLS_FETCH(); - TPHPScriptingEngine *engine; - - trace("ticking %d\n", count); - - engine = (TPHPScriptingEngine*)SG(server_context); - -/* PostThreadMessage(engine->m_enginethread, PHPSE_DUMMY_TICK, 0, 0); */ - - while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - if (msg.hwnd) { - PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); - TranslateMessage(&msg); - DispatchMessage(&msg); - } else { - break; - } - } + zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC); } -/* Synchronize with the engine thread */ -HRESULT TPHPScriptingEngine::SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam) -{ +#if ACTIVEPHP_HAS_OWN_THREAD +struct engine_startup { + HANDLE evt; HRESULT ret; + TPHPScriptingEngine *toclone; + TPHPScriptingEngine *localref; +}; - if (m_enginethread == 0) - return E_UNEXPECTED; - - trace("I'm waiting for a mutex in SendThreadMessage\n this=%08x ethread=%08x msg=%08x\n", - this, m_enginethread, msg); - - tsrm_mutex_lock(m_mutex); - ResetEvent(m_sync_thread_msg); - - /* If we call PostThreadMessage before the thread has created the queue, the message - * posting fails. MSDN docs recommend the following course of action */ - while (!PostThreadMessage(m_enginethread, msg, wparam, lparam)) { - Sleep(50); - if (m_enginethread == 0) { - tsrm_mutex_unlock(m_mutex); - trace("breaking out of dodgy busy wait\n"); - return E_UNEXPECTED; - } - } - - /* Wait for the event object to be signalled. - * This is a nice "blocking without blocking" wait; window messages are dispatched - * and everything works out quite nicely */ - while(1) { - DWORD result = MsgWaitForMultipleObjects(1, &m_sync_thread_msg, FALSE, 4000, QS_ALLINPUT); - - if (result == WAIT_OBJECT_0 + 1) { - /* Dispatch some messages */ - MSG msg; - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - //trace("dispatching message while waiting\n"); - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } else if (result == WAIT_TIMEOUT) { - trace("timeout while waiting for thread reply\n"); - - } else { - /* the event was signalled */ - break; - } - } - ret = m_sync_thread_ret; - ResetEvent(m_sync_thread_msg); - tsrm_mutex_unlock(m_mutex); - return ret; -} +static WNDCLASS wc = {0}; -TPHPScriptingEngine::~TPHPScriptingEngine() +static LRESULT CALLBACK script_thread_msg_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { - trace("\n\n *** Engine Destructor Called\n\n"); + switch (message) { + case WM_ACTIVEPHP_SERIALIZE: + return marshal_stub(lparam); - if (m_scriptstate != SCRIPTSTATE_UNINITIALIZED && m_scriptstate != SCRIPTSTATE_CLOSED && m_enginethread) { - Close(); + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + default: + return DefWindowProc(hwnd, message, wparam, lparam); } - - PostThreadMessage(m_enginethread, WM_QUIT, 0, 0); - WaitForSingleObject(m_engine_thread_handle, INFINITE); - CloseHandle(m_engine_thread_handle); - - TPHPClassFactory::RemoveFromObjectCount(); - tsrm_mutex_free(m_mutex); -} - -/* Set some executor globals and execute a zend_op_array. - * The declaration looks wierd because this can be invoked from - * zend_hash_apply_with_argument */ -static int execute_main(void *pDest, void *arg TSRMLS_DC) -{ - code_frag *frag = *(code_frag**)pDest; - - if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main)) - execute_code_fragment(frag, NULL, NULL TSRMLS_CC); - - return ZEND_HASH_APPLY_KEEP; } -static int clone_frags(void *pDest, void *arg TSRMLS_DC) +static DWORD WINAPI script_thread(LPVOID param) { - code_frag *frag, *src = *(code_frag**)pDest; - TPHPScriptingEngine *engine = (TPHPScriptingEngine*)arg; - - if (src->persistent) { - frag = clone_code_fragment(src, engine TSRMLS_CC); - if (frag) - zend_hash_next_index_insert(&engine->m_frags, &frag, sizeof(code_frag*), NULL); - else - trace("WARNING: clone failed!\n"); - } + struct engine_startup *su = (struct engine_startup*)param; + TPHPScriptingEngine *engine; + IUnknown *punk = NULL; + MSG msg; - return ZEND_HASH_APPLY_KEEP; -} + trace("firing up engine thread/apartment\n"); -HRESULT TPHPScriptingEngine::engine_thread_handler(LONG msg, WPARAM wparam, LPARAM lParam, int *handled TSRMLS_DC) -{ - HRESULT ret = S_OK; + /* set up COM in this apartment */ + CoInitializeEx(0, COINIT_APARTMENTTHREADED); - trace("engine_thread_handler: running in thread %08x, should be %08x msg=%08x this=%08x\n", - tsrm_thread_id(), m_enginethread, msg, this); - - if (handled) - *handled = 1; + /* create a window for message queueing */ + wc.lpfnWndProc = script_thread_msg_proc; + wc.lpszClassName = "ActivePHP Message Window"; + RegisterClass(&wc); - if (m_enginethread == 0) - return E_UNEXPECTED; + /* create the engine state */ + engine = new TPHPScriptingEngine; + + engine->m_enginethread = tsrm_thread_id(); + engine->m_basethread = 0; + engine->m_queue = CreateWindow(wc.lpszClassName, wc.lpszClassName, + 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, NULL, NULL); + + /* marshall it for another apartment */ + engine->QueryInterface(IID_IUnknown, (void**)&punk); + su->ret = GIT_put(punk, IID_IUnknown, &engine->m_gitcookie); + punk->Release(); + su->localref = engine; + + /* do we need to clone ? */ + if (su->toclone) { + engine->do_clone(su->toclone); + } - switch(msg) { - case PHPSE_ADD_TYPELIB: - { - struct php_active_script_add_tlb_info *info = (struct php_active_script_add_tlb_info*)lParam; - ITypeLib *TypeLib; + /* tell whoever spawned us that we're ready and waiting */ + SetEvent(su->evt); - if (SUCCEEDED(LoadRegTypeLib(*info->rguidTypeLib, (USHORT)info->dwMajor, - (USHORT)info->dwMinor, LANG_NEUTRAL, &TypeLib))) { - php_com_import_typelib(TypeLib, CONST_CS, CP_ACP TSRMLS_CC); - TypeLib->Release(); - } - } - break; - case PHPSE_STATE_CHANGE: - { - /* handle the state change here */ - SCRIPTSTATE ss = (SCRIPTSTATE)lParam; - int start_running = 0; - trace("%08x: DoSetScriptState(current=%s, new=%s)\n", - this, - scriptstate_to_string(m_scriptstate), - scriptstate_to_string(ss)); - - if (m_scriptstate == SCRIPTSTATE_INITIALIZED && (ss == SCRIPTSTATE_STARTED || ss == SCRIPTSTATE_CONNECTED)) - start_running = 1; - - m_scriptstate = ss; - - /* inform host/site of the change */ - if (m_pass_eng) - m_pass_eng->OnStateChange(m_scriptstate); - - if (start_running) { - /* run "main()", as described in the docs */ - if (m_pass_eng) - m_pass_eng->OnEnterScript(); - trace("%08x: apply execute main to m_frags\n", this); - m_in_main = 1; - m_stop_main = 0; - zend_hash_apply_with_argument(&m_frags, execute_main, this TSRMLS_CC); - m_in_main = 0; - trace("%08x: --- done execute main\n", this); - if (m_pass_eng) - m_pass_eng->OnLeaveScript(); - - /* docs are a bit ambiguous here, but it appears that we should - * inform the host that the main script execution has completed, - * and also what the return value is */ - VARIANT varRes; - - VariantInit(&varRes); - if (m_pass_eng) - m_pass_eng->OnScriptTerminate(&varRes, NULL); - - /* - m_scriptstate = SCRIPTSTATE_INITIALIZED; - if (m_pass_eng) - m_pass_eng->OnStateChange(m_scriptstate); - */ - } - } - break; - case PHPSE_INIT_NEW: - { - /* Prepare PHP/ZE for use */ - - trace("%08x: m_frags : INIT NEW\n", this); - -#if 0 - zend_hash_init(&m_frags, 0, NULL, frag_dtor, TRUE); - - SG(options) |= SAPI_OPTION_NO_CHDIR; - SG(server_context) = this; - /* override the default PHP error callback */ - zend_error_cb = activescript_error_handler; - - zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - - php_request_startup(TSRMLS_C); - PG(during_request_startup) = 0; -// trace("\n\n *** ticks func at %08x %08x ***\n\n\n", activescript_run_ticks, &activescript_run_ticks); -// php_add_tick_function(activescript_run_ticks); -#endif - - } - break; - case PHPSE_CLOSE: - { - /* Close things down */ - trace("%08x: m_frags : CLOSE/DESTROY\n", this); - m_scriptstate = SCRIPTSTATE_CLOSED; - if (m_pass_eng) { - m_pass_eng->OnStateChange(m_scriptstate); - trace("%08x: release site from this side\n", this); - m_pass_eng->Release(); - m_pass_eng = NULL; - } -#if 0 - zend_hash_destroy(&m_frags); - php_request_shutdown(NULL); -#endif - - break; - } - break; - case PHPSE_CLONE: - { - /* Clone the engine state. This is semantically equal to serializing all - * the parsed code from the source and unserializing it in the dest (this). - * IE doesn't appear to use it, but Windows Script Host does. I'd expect - * ASP/ASP.NET to do so also. - * - * FIXME: Probably won't work with IActiveScriptParseProcedure scriplets - * */ - - TPHPScriptingEngine *src = (TPHPScriptingEngine*)lParam; - - trace("%08x: m_frags : CLONE\n", this); - zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC); - - } - break; - case PHPSE_ADD_SCRIPTLET: - { - /* Parse/compile a chunk of script that will act as an event handler. - * If the host supports IActiveScriptParseProcedure, this code will - * not be called. - * The docs are (typically) vague: AFAICT, once the code has been - * compiled, we are supposed to arrange for an IConnectionPoint - * advisory connection to the item/subitem, once the script - * moves into SCRIPTSTATE_CONNECTED. - * That's a lot of work! - * - * FIXME: this is currently almost useless - * */ - - struct php_active_script_add_scriptlet_info *info = (struct php_active_script_add_scriptlet_info*)lParam; - - TWideString - default_name(info->pstrDefaultName), - code(info->pstrCode), - item_name(info->pstrItemName), - sub_item_name(info->pstrSubItemName), - event_name(info->pstrEventName), - delimiter(info->pstrDelimiter); - - /* lets invent a function name for the scriptlet */ - char sname[256]; - - /* should check if the name is already used! */ - if (info->pstrDefaultName) - strcpy(sname, default_name.ansi_string()); - else { - sname[0] = 0; - strcat(sname, "__"); - if (info->pstrItemName) { - strcat(sname, item_name.ansi_string()); - strcat(sname, "_"); - } - if (info->pstrSubItemName) { - strcat(sname, sub_item_name.ansi_string()); - strcat(sname, "_"); - } - if (info->pstrEventName) - strcat(sname, event_name.ansi_string()); - } - - - trace("%08x: AddScriptlet:\n state=%s\n name=%s\n code=%s\n item=%s\n subitem=%s\n event=%s\n delim=%s\n line=%d\n", - this, scriptstate_to_string(m_scriptstate), - default_name.safe_ansi_string(), code.safe_ansi_string(), item_name.safe_ansi_string(), - sub_item_name.safe_ansi_string(), event_name.safe_ansi_string(), delimiter.safe_ansi_string(), - info->ulStartingLineNumber); - - - code_frag *frag = compile_code_fragment( - FRAG_SCRIPTLET, - sname, - info->pstrCode, - info->ulStartingLineNumber, - info->pexcepinfo, - this - TSRMLS_CC); - - if (frag) { - - frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); - - zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); - - /* - ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; - - disp->AddRef(); - disp->m_frag = frag; - disp->m_procflags = info->dwFlags; - disp->m_engine = this; - frag->ptr = disp; - - *info->ppdisp = disp; - */ - ret = S_OK; - } else { - ret = DISP_E_EXCEPTION; - } - - *info->pbstrName = TWideString::bstr_from_ansi(sname); - - trace("%08x: done with scriptlet %s\n", this, sname); - - } - break; - case PHPSE_GET_DISPATCH: - { - struct php_active_script_get_dispatch_info *info = (struct php_active_script_get_dispatch_info *)lParam; - IDispatch *disp = NULL; - - if (info->pstrItemName != NULL) { - zval **tmp; - /* use this rather than php_OLECHAR_to_char because we want to avoid emalloc here */ - TWideString itemname(info->pstrItemName); - - /* Get that item from the global namespace. - * If it is an object, export it as a dispatchable object. - * */ - - if (zend_hash_find(&EG(symbol_table), itemname.ansi_string(), - itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) { - if (Z_TYPE_PP(tmp) == IS_OBJECT) { - /* FIXME: if this causes an allocation (emalloc) and we are - * not in the engine thread, things could get ugly!!! */ - disp = php_com_wrapper_export(*tmp TSRMLS_CC); - } - } - - } else { - /* This object represents PHP global namespace */ - disp = (IDispatch*) new ScriptDispatch; - } + /* pump COM messages */ + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } - if (disp) { - ret = GIT_put(disp, IID_IDispatch, &info->dispatch); - disp->Release(); - trace("GET_DISPATCH: we put it in the GIT\n"); - } else { - ret = S_FALSE; - trace("GET_DISPATCH: FAILED to put it in the GIT\n"); - } - } - break; - case PHPSE_ADD_NAMED_ITEM: - { - /* The Host uses this to add objects to the global namespace. - * Some objects are intended to have their child properties - * globally visible, so we add those to the global namespace too. - * */ - struct php_active_script_add_named_item_info *info = (struct php_active_script_add_named_item_info *)lParam; - - TWideString name(info->pstrName); - IDispatch *disp = NULL; - HRESULT res; - - trace("ADD_NAMED_ITEM\n"); + trace("terminating engine thread\n"); + + CoUninitialize(); -#if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED - res = info->punk->QueryInterface(IID_IDispatch, (void**)&disp); -#else - res = GIT_get(info->marshal, IID_IDispatch, (void**)&disp); + return 0; +} #endif - if (SUCCEEDED(res) && disp) { - add_to_global_namespace(disp, info->dwFlags, name.ansi_string() TSRMLS_CC); - disp->Release(); - } else { - trace("Ouch: failed to get IDispatch for %s from GIT '%s'\n", name.ansi_string(), php_win_err(res)); - } - } - break; - case PHPSE_SET_SITE: - { - if (m_pass_eng) { - m_pass_eng->Release(); - m_pass_eng = NULL; - } +IUnknown *create_scripting_engine(TPHPScriptingEngine *tobecloned) +{ + IUnknown *punk = NULL; +#if ACTIVEPHP_HAS_OWN_THREAD + struct engine_startup su; + HANDLE hthr; + DWORD thid; - if (lParam) - GIT_get(lParam, IID_IActiveScriptSite, (void**)&m_pass_eng); + su.evt = CreateEvent(NULL, TRUE, FALSE, NULL); + su.toclone = tobecloned; - trace("%08x: site (engine-side) is now %08x (base=%08x)\n", this, m_pass_eng, m_pass); + hthr = CreateThread(NULL, 0, script_thread, &su, 0, &thid); + if (hthr) + CloseHandle(hthr); - } - break; - case PHPSE_EXEC_PROC: - { - ScriptProcedureDispatch *disp = (ScriptProcedureDispatch *)lParam; - execute_code_fragment(disp->m_frag, NULL, NULL TSRMLS_CC); - } - break; - case PHPSE_PARSE_PROC: - { - /* This is the IActiveScriptParseProcedure implementation. - * IE uses this to request for an IDispatch that it will invoke in - * response to some event, and tells us the code that it wants to - * run. - * We compile the code and pass it back a dispatch object. - * The object will then serialize access to the engine thread and - * execute the opcodes */ - struct php_active_script_parse_proc_info *info = (struct php_active_script_parse_proc_info*)lParam; - TWideString - formal_params(info->pstrFormalParams), - procedure_name(info->pstrProcedureName), - item_name(info->pstrItemName), - delimiter(info->pstrDelimiter); - - trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n", - this, scriptstate_to_string(m_scriptstate), - formal_params.ansi_string(), procedure_name.ansi_string(), - item_name.safe_ansi_string(), delimiter.safe_ansi_string(), - info->ulStartingLineNumber); - - - code_frag *frag = compile_code_fragment( - FRAG_PROCEDURE, - NULL, - info->pstrCode, - info->ulStartingLineNumber, - NULL, - this - TSRMLS_CC); - - if (frag) { - - frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); - zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); - - ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; - - disp->m_frag = frag; - disp->m_procflags = info->dwFlags; - disp->m_engine = this; - frag->ptr = disp; - info->dispcookie = disp->m_gitcookie; - - } else { - ret = DISP_E_EXCEPTION; - } + WaitForSingleObject(su.evt, INFINITE); - } - break; + if (SUCCEEDED(su.ret)) { + punk = (IUnknown*)(void*)su.localref; + } - case PHPSE_PARSE_SCRIPT: - { - struct php_active_script_parse_info *info = (struct php_active_script_parse_info*)lParam; - int doexec; - - TWideString - code(info->pstrCode), - item_name(info->pstrItemName), - delimiter(info->pstrDelimiter); - - trace("%08x: ParseScriptText:\n state=%s\ncode=%s\n item=%s\n delim=%s\n line=%d\n", - this, scriptstate_to_string(m_scriptstate), - code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(), - info->ulStartingLineNumber); - - code_frag *frag = compile_code_fragment( - FRAG_MAIN, - info->dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL, - info->pstrCode, - info->ulStartingLineNumber, - info->pexcepinfo, - this - TSRMLS_CC); - - doexec = (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) || - m_scriptstate == SCRIPTSTATE_STARTED || - m_scriptstate == SCRIPTSTATE_CONNECTED || - m_scriptstate == SCRIPTSTATE_DISCONNECTED; - - if (frag) { - frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); - ret = S_OK; + CloseHandle(su.evt); +#else + punk = (IActiveScript*)new TPHPScriptingEngine; +#endif + return punk; +} - if (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) { - if (m_scriptstate == SCRIPTSTATE_INITIALIZED) { - /* not allowed to execute code in this state */ - ret = E_UNEXPECTED; - doexec = 0; - } - } +void TPHPScriptingEngine::setup_engine_state(void) +{ + TSRMLS_FETCH(); - if (doexec) { - /* execute the code as an expression */ - if (!execute_code_fragment(frag, info->pvarResult, info->pexcepinfo TSRMLS_CC)) - ret = DISP_E_EXCEPTION; - } + m_enginethread = tsrm_thread_id(); + trace("initializing zend engine on this thread\n"); + + SG(options) |= SAPI_OPTION_NO_CHDIR; + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; - zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); - } else { - ret = DISP_E_EXCEPTION; - } + SG(server_context) = this; - if (info->pvarResult) { - VariantInit(info->pvarResult); - } + /* override the default PHP error callback */ + zend_error_cb = activescript_error_handler; + zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - } - break; - default: - trace("XXXXX: unhandled message type %08x\n", msg); - if (handled) - *handled = 0; - } - return ret; + php_request_startup(TSRMLS_C); + PG(during_request_startup) = 0; + zend_hash_init(&m_frags, 0, NULL, frag_dtor, 0); + + m_done_init = 1; + + trace("---- init done\n"); } -/* The PHP/Zend state actually lives in this thread */ -void TPHPScriptingEngine::engine_thread_func(void) +TPHPScriptingEngine::TPHPScriptingEngine() { TSRMLS_FETCH(); - int handled; - int terminated = 0; - MSG msg; + + /* CTOR */ - trace("%08x: engine thread started up!\n", this); + trace("*** NEW this=%08x\n", this); - CoInitializeEx(0, ACTIVEPHP_THREADING_MODE); - - zend_first_try { - m_tsrm_hack = tsrm_ls; - - SG(options) |= SAPI_OPTION_NO_CHDIR; - SG(headers_sent) = 1; - SG(request_info).no_headers = 1; - - SG(server_context) = this; - /* override the default PHP error callback */ - zend_error_cb = activescript_error_handler; - - zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); - - if (SUCCESS == php_request_startup(TSRMLS_C)) { - PG(during_request_startup) = 0; - if (FAILURE == zend_hash_init(&m_frags, 0, NULL, frag_dtor, 1)) { - trace("failed to init frags hash\n"); - } + m_scriptstate = SCRIPTSTATE_UNINITIALIZED; + m_pass = NULL; + m_in_main = 0; + m_done_init = 0; + m_stop_main = 0; + m_lambda_count = 0; + m_refcount = 1; + m_ids = 0; + TPHPClassFactory::AddToObjectCount(); - while(!terminated) { - DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 4000, QS_ALLINPUT); +#if ACTIVEPHP_HAS_OWN_THREAD + setup_engine_state(); +#endif - switch(result) { - case WAIT_OBJECT_0: - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { +} - if (msg.message == WM_QUIT) { - terminated = 1; - } else if (msg.hwnd) { - TranslateMessage(&msg); - DispatchMessage(&msg); +TPHPScriptingEngine::~TPHPScriptingEngine() +{ + /* DTOR */ - } else { - handled = 1; - m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC); - if (handled) - SetEvent(m_sync_thread_msg); - } + trace("\n\n *** Engine Destructor Called\n\n"); - } - break; - case WAIT_TIMEOUT: - trace("thread wait timed out\n"); - break; - default: - trace("some strange value\n"); - } - } + if (m_done_init && m_scriptstate != SCRIPTSTATE_CLOSED) { + Close(); + } -#if 0 - while(GetMessage(&msg, NULL, 0, 0)) { + while (m_ids--) { + CoTaskMemFree(m_names[m_ids]); + } - if (msg.message == WM_QUIT) - break; + TPHPClassFactory::RemoveFromObjectCount(); +} - handled = 1; - m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC); - if (handled) - SetEvent(m_sync_thread_msg); - } -#endif +/* Set some executor globals and execute a zend_op_array. + * The declaration looks wierd because this can be invoked from + * zend_hash_apply_with_argument */ +static int execute_main(void *pDest, void *arg TSRMLS_DC) +{ + code_frag *frag = *(code_frag**)pDest; - trace("%08x: engine thread exiting!!!!!\n", this); - zend_hash_destroy(&m_frags); - trace("calling request shutdown\n"); - php_request_shutdown(NULL); - } else { - trace("request startup failed\n"); - } + if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main)) + execute_code_fragment(frag, NULL, NULL TSRMLS_CC); - } zend_catch { - trace("ouch: bailed out\n"); - } zend_end_try(); - - m_enginethread = 0; - CoUninitialize(); + return ZEND_HASH_APPLY_KEEP; } +static int clone_frags(void *pDest, void *arg TSRMLS_DC) +{ + code_frag *frag, *src = *(code_frag**)pDest; + TPHPScriptingEngine *engine = (TPHPScriptingEngine*)arg; + + if (src->persistent) { + frag = clone_code_fragment(src, engine TSRMLS_CC); + if (frag) + zend_hash_next_index_insert(&engine->m_frags, &frag, sizeof(code_frag*), NULL); + else + trace("WARNING: clone failed!\n"); + } + + return ZEND_HASH_APPLY_KEEP; +} + /* Only call this in the context of the engine thread, or you'll be sorry. * * When SCRIPTITEM_GLOBALMEMBERS is set, we're only adding COM objects to the namespace. * We could add *all* properties, but I don't like this idea; what if the value changes * while the page is running? We'd be left with stale data. - * - * TODO: evaluate if it is appropriate to register as an auto_global * */ static inline void make_auto_global(char *name, zval *val TSRMLS_DC) { int namelen = strlen(name); - +trace("make_auto_global %s\n", name); zend_register_auto_global(name, namelen, NULL TSRMLS_CC); zend_auto_global_disable_jit(name, namelen TSRMLS_CC); ZEND_SET_SYMBOL(&EG(symbol_table), name, val); @@ -1423,7 +1146,10 @@ trace("Add %s to global namespace\n", name); STDMETHODIMP_(DWORD) TPHPScriptingEngine::AddRef(void) { - return InterlockedIncrement(const_cast (&m_refcount)); + DWORD ret; + ret = InterlockedIncrement(const_cast (&m_refcount)); + trace("AddRef --> %d\n", ret); + return ret; } STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void) @@ -1433,25 +1159,26 @@ STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void) trace("%08x: Release: zero refcount, destroy the engine!\n", this); delete this; } + trace("Release --> %d\n", ret); return ret; } STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) { *ppvObject = NULL; - + if (IsEqualGUID(IID_IActiveScript, iid)) { *ppvObject = (IActiveScript*)this; } else if (IsEqualGUID(IID_IActiveScriptParse, iid)) { *ppvObject = (IActiveScriptParse*)this; } else if (IsEqualGUID(IID_IActiveScriptParseProcedure, iid)) { *ppvObject = (IActiveScriptParseProcedure*)this; -#if ACTIVEPHP_OBJECT_SAFETY } else if (IsEqualGUID(IID_IObjectSafety, iid)) { *ppvObject = (IObjectSafety*)this; -#endif } else if (IsEqualGUID(IID_IUnknown, iid)) { *ppvObject = this; + } else if (IsEqualGUID(IID_IDispatch, iid)) { + *ppvObject = (IDispatch*)this; } else { LPOLESTR guidw; StringFromCLSID(iid, &guidw); @@ -1465,7 +1192,7 @@ STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) AddRef(); return S_OK; } - + return E_NOINTERFACE; } @@ -1473,38 +1200,53 @@ STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) * It also defines the base thread. */ STDMETHODIMP TPHPScriptingEngine::SetScriptSite(IActiveScriptSite *pass) { + HRESULT ret = S_OK; TSRMLS_FETCH(); - tsrm_mutex_lock(m_mutex); + if (m_pass && pass) { + trace("SetScriptSite: we're already set\n"); + return E_FAIL; + } + + trace("%08x: SetScriptSite(%08x) -----> Base thread is %08x\n", this, pass, tsrm_thread_id()); - trace("%08x: -----> Base thread is %08x\n", this, tsrm_thread_id()); + if (pass) { + m_basethread = tsrm_thread_id(); +#if ACTIVEPHP_HAS_OWN_THREAD + HRESULT ret = GIT_put(pass, IID_IActiveScriptSite, &m_asscookie); +#endif + } if (m_pass) { +#if ACTIVEPHP_HAS_OWN_THREAD + trace("killing off ass cookie\n"); + GIT_revoke(m_asscookie, m_pass); +#endif m_pass->Release(); m_pass = NULL; - SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, 0 TSRMLS_CC); } if (pass == NULL) { trace("Closing down site; we should have no references to objects from the host\n" - " m_pass=%08x\n m_pass_eng=%08x\n What about named items??\n", - m_pass, m_pass_eng); + " m_pass=%08x\n What about named items??\n", + m_pass); } - m_pass = pass; - if (m_pass) { - m_pass->AddRef(); - - DWORD cookie; - if (SUCCEEDED(GIT_put(m_pass, IID_IActiveScriptSite, &cookie))) - SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, cookie TSRMLS_CC); + if (pass) { + trace("taking a ref on pass\n"); + ret = pass->QueryInterface(IID_IActiveScriptSite, (void**)&m_pass); + trace("----> %s", php_win_err(ret)); - if (m_scriptstate == SCRIPTSTATE_UNINITIALIZED) - SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, SCRIPTSTATE_INITIALIZED TSRMLS_CC); + if (SUCCEEDED(ret)) { + +#if !ACTIVEPHP_HAS_OWN_THREAD + setup_engine_state(); +#endif + SetScriptState(SCRIPTSTATE_INITIALIZED); + } } - tsrm_mutex_unlock(m_mutex); - return S_OK; + return ret; } STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject) @@ -1512,87 +1254,148 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject) HRESULT ret = S_FALSE; trace("%08x: GetScriptSite()\n", this); - tsrm_mutex_lock(m_mutex); + if (m_pass) { + ASS_CALL(ret, QueryInterface, (riid, ppvObject)) + } - if (m_pass) - ret = m_pass->QueryInterface(riid, ppvObject); - - tsrm_mutex_unlock(m_mutex); return ret; } STDMETHODIMP TPHPScriptingEngine::SetScriptState(SCRIPTSTATE ss) { + HRESULT dummy = E_UNEXPECTED; + int start_running = 0; TSRMLS_FETCH(); - trace("%08x: SetScriptState(%s)\n", this, scriptstate_to_string(ss)); - return SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, ss TSRMLS_CC); + + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_SetScriptState, 1, ss); + + trace("%08x: SetScriptState(current=%s, new=%s)\n", + this, + scriptstate_to_string(m_scriptstate), + scriptstate_to_string(ss)); + + if (m_scriptstate == SCRIPTSTATE_INITIALIZED && (ss == SCRIPTSTATE_STARTED || ss == SCRIPTSTATE_CONNECTED)) + start_running = 1; + + m_scriptstate = ss; + + if (start_running) { + /* run "main()", as described in the docs */ + if (m_pass) { + ASS_CALL(dummy, OnEnterScript, ()); + } + trace("%08x: apply execute main to m_frags\n", this); + m_in_main = 1; + m_stop_main = 0; + zend_hash_apply_with_argument(&m_frags, execute_main, this TSRMLS_CC); + m_in_main = 0; + trace("%08x: --- done execute main\n", this); + + if (m_pass) { + ASS_CALL(dummy, OnLeaveScript, ()); + } + } + + /* inform host/site of the change */ + if (m_pass) { +#if 0 + if (ss == SCRIPTSTATE_INITIALIZED) { + VARIANT varRes; + VariantInit(&varRes); + +// ASS_CALL(dummy, OnScriptTerminate, (&varRes, NULL)); + } +#endif + + ASS_CALL(dummy, OnStateChange, (m_scriptstate)); + } + + + return S_OK; } STDMETHODIMP TPHPScriptingEngine::GetScriptState(SCRIPTSTATE *pssState) { trace("%08x: GetScriptState(current=%s)\n", this, scriptstate_to_string(m_scriptstate)); - tsrm_mutex_lock(m_mutex); *pssState = m_scriptstate; - tsrm_mutex_unlock(m_mutex); return S_OK; } STDMETHODIMP TPHPScriptingEngine::Close(void) { TSRMLS_FETCH(); + + trace("Close() refcount = %d\n", m_refcount); - if (m_pass) { + if (m_scriptstate == SCRIPTSTATE_CLOSED) + return E_UNEXPECTED; + + if (m_done_init) { + + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_Close, 0); + + m_done_init = 0; + m_scriptstate = SCRIPTSTATE_CLOSED; + zend_hash_destroy(&m_frags); + php_request_shutdown(NULL); + m_enginethread = 0; + } + + + if (m_pass && tsrm_thread_id() == m_basethread) { + m_pass->OnStateChange(m_scriptstate); + } + + //GIT_revoke(m_asscookie, m_pass); + trace("%08x: release site \n", this); + + if (m_pass && tsrm_thread_id() == m_basethread) { m_pass->Release(); m_pass = NULL; } - SEND_THREAD_MESSAGE(this, PHPSE_CLOSE, 0, 0 TSRMLS_CC); + return S_OK; } -/* Add an item to global namespace. - * This is called in the context of the base thread (or perhaps some other thread). - * We want to be able to work with the object in the engine thread, so we marshal - * it into a stream and let the engine thread deal with it. - * This works quite nicely when PHP scripts call into the object; threading is - * handled correctly. */ +/* The Host uses this to add objects to the global namespace. Some objects are + * intended to have their child properties globally visible, so we add those to + * the global namespace too. */ + STDMETHODIMP TPHPScriptingEngine::AddNamedItem(LPCOLESTR pstrName, DWORD dwFlags) { - struct php_active_script_add_named_item_info info; HRESULT res; + IUnknown *punk = NULL; + ITypeInfo *ti = NULL; TSRMLS_FETCH(); - info.pstrName = pstrName; - info.dwFlags = dwFlags; - - res = m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &info.punk, NULL); + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_AddNamedItem, 2, pstrName, dwFlags); + + TWideString name(pstrName); + trace("AddNamedItem: %s (%08x) m_pass=%08x\n", name.ansi_string(), dwFlags, m_pass); + + res = m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &punk, &ti); if (SUCCEEDED(res)) { -#if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED - /* strangely, the GIT doesn't want to give the engine thread the interface - * in this threading mode */ - SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); -#else - IDispatch *disp; - res = info.punk->QueryInterface(IID_IDispatch, (void**)&disp); - + IDispatch *disp = NULL; + + trace("ADD_NAMED_ITEM\n"); + + res = punk->QueryInterface(IID_IDispatch, (void**)&disp); if (SUCCEEDED(res) && disp) { - if (SUCCEEDED(GIT_put(disp, IID_IDispatch, &info.marshal))) { - trace("put disp=%p into git with marshal ID of %x\n", disp, info.marshal); - SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); - GIT_revoke(info.marshal, disp); - } + add_to_global_namespace(disp, dwFlags, name.ansi_string() TSRMLS_CC); disp->Release(); } else { - trace("failed to get IDispatch from punk %s", php_win_err(res)); + trace("Ouch: failed to get IDispatch for %s from GIT '%s'\n", name.ansi_string(), php_win_err(res)); } - info.punk->Release(); -#endif + } else { trace("failed to get named item, %s", php_win_err(res)); } - - return S_OK; + return res; } /* Bind to a type library */ @@ -1602,17 +1405,25 @@ STDMETHODIMP TPHPScriptingEngine::AddTypeLib( /* [in] */ DWORD dwMinor, /* [in] */ DWORD dwFlags) { - struct php_active_script_add_tlb_info info; + HRESULT ret; + ITypeLib *TypeLib; TSRMLS_FETCH(); - info.rguidTypeLib = &rguidTypeLib; - info.dwMajor = dwMajor; - info.dwMinor = dwMinor; - info.dwFlags = dwFlags; + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_AddTypeLib, 4, rguidTypeLib, dwMajor, dwMinor, dwFlags); - SEND_THREAD_MESSAGE(this, PHPSE_ADD_TYPELIB, 0, (LPARAM)&info TSRMLS_CC); + ENGINE_THREAD_ONLY(IActiveScript, AddTypeLib); + + trace("AddTypeLib\n"); + ret = LoadRegTypeLib(rguidTypeLib, (USHORT)dwMajor, (USHORT)dwMinor, LANG_NEUTRAL, &TypeLib); - return S_OK; + if (SUCCEEDED(ret)) { + php_com_import_typelib(TypeLib, CONST_CS, CP_ACP TSRMLS_CC); + TypeLib->Release(); + } + + + return ret; } /* Returns an object representing the PHP Scripting Engine. @@ -1624,34 +1435,40 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch( /* [in] */ LPCOLESTR pstrItemName, /* [out] */ IDispatch **ppdisp) { + zend_function *func = NULL; TSRMLS_FETCH(); - *ppdisp = NULL; - struct php_active_script_get_dispatch_info info; - - info.pstrItemName = pstrItemName; - info.dispatch = NULL; - - /* This hack is required because the host is likely to query us - * for a dispatch if we use any of its objects from PHP script. - * Since the engine thread will be waiting for the return from - * a COM call, we need to deliberately poke a hole in thread - * safety so that it is possible to read the symbol table from - * outside the engine thread and give it a valid return value. - * This is "safe" only in this instance, since we are not modifying - * the engine state by looking up the dispatch (I hope). - * The scripting engine rules pretty much guarantee that this - * method is only called in the base thread. - * This appears to only happen when we set the threading to - * apartment. */ if (tsrm_thread_id() != m_enginethread) { - tsrm_ls = m_tsrm_hack; - trace("HEY: hacking thread safety!\n"); + return marshal_call(this, APHP_GetScriptDispatch, 2, pstrItemName, ppdisp); } + + *ppdisp = NULL; + + if (pstrItemName != NULL) { + zval **tmp; + TWideString itemname(pstrItemName); + trace("GetScriptDispatch %s\n", itemname.ansi_string()); + + /* Get that item from the global namespace. + * If it is an object, export it as a dispatchable object. + * */ + + if (zend_hash_find(&EG(symbol_table), itemname.ansi_string(), + itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) { + if (Z_TYPE_PP(tmp) == IS_OBJECT) { + *ppdisp = php_com_wrapper_export(*tmp TSRMLS_CC); + } + } else if (zend_hash_find(EG(function_table), itemname.ansi_string(), + itemname.ansi_len() + 1, (void**)&func) == SUCCESS) { + trace("The host wants a function, but we don't have one\n"); + } - if (S_OK == engine_thread_handler(PHPSE_GET_DISPATCH, 0, (LPARAM)&info, NULL TSRMLS_CC)) { - GIT_get(info.dispatch, IID_IDispatch, (void**)ppdisp); + } else { + trace("GetScriptDispatch NULL\n"); + /* This object represents PHP global namespace */ + *ppdisp = (IDispatch*)this; } + if (*ppdisp) { return S_OK; @@ -1662,10 +1479,8 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch( STDMETHODIMP TPHPScriptingEngine::GetCurrentScriptThreadID( /* [out] */ SCRIPTTHREADID *pstidThread) { -// tsrm_mutex_lock(m_mutex); trace("%08x: GetCurrentScriptThreadID()\n", this); - *pstidThread = m_enginethread; -// tsrm_mutex_unlock(m_mutex); + *pstidThread = GetCurrentThreadId();//m_enginethread; return S_OK; } @@ -1673,10 +1488,8 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptThreadID( /* [in] */ DWORD dwWin32ThreadId, /* [out] */ SCRIPTTHREADID *pstidThread) { -// tsrm_mutex_lock(m_mutex); trace("%08x: GetScriptThreadID()\n", this); *pstidThread = dwWin32ThreadId; -// tsrm_mutex_unlock(m_mutex); return S_OK; } @@ -1684,11 +1497,8 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState( /* [in] */ SCRIPTTHREADID stidThread, /* [out] */ SCRIPTTHREADSTATE *pstsState) { -// tsrm_mutex_lock(m_mutex); - trace("%08x: GetScriptThreadState()\n", this); *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; - switch(stidThread) { case SCRIPTTHREADID_BASE: stidThread = m_basethread; @@ -1702,7 +1512,6 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState( } else if (stidThread == m_enginethread) { *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; } -// tsrm_mutex_unlock(m_mutex); return S_OK; } @@ -1724,31 +1533,34 @@ STDMETHODIMP TPHPScriptingEngine::InterruptScriptThread( STDMETHODIMP TPHPScriptingEngine::Clone( /* [out] */ IActiveScript **ppscript) { - TPHPScriptingEngine *cloned = new TPHPScriptingEngine; - TSRMLS_FETCH(); + HRESULT ret = E_FAIL; + IUnknown *punk; - trace("%08x: Clone()\n", this); - - if (ppscript) - *ppscript = NULL; - - if (cloned) { - cloned->InitNew(); - SEND_THREAD_MESSAGE(cloned, PHPSE_CLONE, 0, (LPARAM)this TSRMLS_CC); - trace("%08x: Cloned OK, returning cloned object ptr %08x\n", this, cloned); - *ppscript = (IActiveScript*)cloned; - return S_OK; - + trace("************* Clone this[%08x]\n", this); + + punk = create_scripting_engine(this); + + if (punk) { + ret = punk->QueryInterface(IID_IActiveScript, (void**)ppscript); + punk->Release(); } - - return E_FAIL; + + return ret; } STDMETHODIMP TPHPScriptingEngine::InitNew( void) { TSRMLS_FETCH(); - SEND_THREAD_MESSAGE(this, PHPSE_INIT_NEW, 0, 0 TSRMLS_CC); + trace("InitNew() this=%08x\n", this); + if (m_done_init) { + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_InitNew, 0); + + zend_hash_destroy(&m_frags); + php_request_shutdown(NULL); + setup_engine_state(); + } return S_OK; } @@ -1765,22 +1577,102 @@ STDMETHODIMP TPHPScriptingEngine::AddScriptlet( /* [out] */ BSTR *pbstrName, /* [out] */ EXCEPINFO *pexcepinfo) { - struct php_active_script_add_scriptlet_info info; + HRESULT ret; TSRMLS_FETCH(); - info.pstrDefaultName = pstrDefaultName; - info.pstrCode = pstrCode; - info.pstrItemName = pstrItemName; - info.pstrSubItemName = pstrSubItemName; - info.pstrEventName = pstrEventName; - info.pstrDelimiter = pstrDelimiter; - info.dwSourceContextCookie = dwSourceContextCookie; - info.ulStartingLineNumber = ulStartingLineNumber; - info.dwFlags = dwFlags; - info.pbstrName = pbstrName; - info.pexcepinfo = pexcepinfo; - - return SEND_THREAD_MESSAGE(this, PHPSE_ADD_SCRIPTLET, 0, (LPARAM)&info TSRMLS_CC); + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_AddScriptlet, 11, pstrDefaultName, pstrCode, pstrItemName, pstrSubItemName, pstrEventName, pstrDelimiter, dwSourceContextCookie, ulStartingLineNumber, dwFlags, pbstrName, pexcepinfo); + + ENGINE_THREAD_ONLY(IActiveScriptParse, AddScriptlet); + +trace("AddScriptlet\n"); + + /* Parse/compile a chunk of script that will act as an event handler. + * If the host supports IActiveScriptParseProcedure, this code will + * not be called. + * The docs are (typically) vague: AFAICT, once the code has been + * compiled, we are supposed to arrange for an IConnectionPoint + * advisory connection to the item/subitem, once the script + * moves into SCRIPTSTATE_CONNECTED. + * That's a lot of work! + * + * FIXME: this is currently almost useless + * */ + + TWideString + default_name(pstrDefaultName), + code(pstrCode), + item_name(pstrItemName), + sub_item_name(pstrSubItemName), + event_name(pstrEventName), + delimiter(pstrDelimiter); + + /* lets invent a function name for the scriptlet */ + char sname[256]; + + /* should check if the name is already used! */ + if (pstrDefaultName) + strcpy(sname, default_name.ansi_string()); + else { + sname[0] = 0; + strcat(sname, "__"); + if (pstrItemName) { + strcat(sname, item_name.ansi_string()); + strcat(sname, "_"); + } + if (pstrSubItemName) { + strcat(sname, sub_item_name.ansi_string()); + strcat(sname, "_"); + } + if (pstrEventName) + strcat(sname, event_name.ansi_string()); + } + + + trace("%08x: AddScriptlet:\n state=%s\n name=%s\n code=%s\n item=%s\n subitem=%s\n event=%s\n delim=%s\n line=%d\n", + this, scriptstate_to_string(m_scriptstate), + default_name.safe_ansi_string(), code.safe_ansi_string(), item_name.safe_ansi_string(), + sub_item_name.safe_ansi_string(), event_name.safe_ansi_string(), delimiter.safe_ansi_string(), + ulStartingLineNumber); + + + code_frag *frag = compile_code_fragment( + FRAG_SCRIPTLET, + sname, + pstrCode, + ulStartingLineNumber, + pexcepinfo, + this + TSRMLS_CC); + + if (frag) { + + frag->persistent = (dwFlags & SCRIPTTEXT_ISPERSISTENT); + + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + + /* + ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; + + disp->AddRef(); + disp->m_frag = frag; + disp->m_procflags = info->dwFlags; + disp->m_engine = this; + frag->ptr = disp; + + *ppdisp = disp; + */ + ret = S_OK; + } else { + ret = DISP_E_EXCEPTION; + } + + *pbstrName = TWideString::bstr_from_ansi(sname); + + trace("%08x: done with scriptlet %s\n", this, sname); + + + return ret; } STDMETHODIMP TPHPScriptingEngine::ParseScriptText( @@ -1794,20 +1686,74 @@ STDMETHODIMP TPHPScriptingEngine::ParseScriptText( /* [out] */ VARIANT *pvarResult, /* [out] */ EXCEPINFO *pexcepinfo) { - struct php_active_script_parse_info info; +trace("ParseScriptText\n"); + int doexec; + TWideString + code(pstrCode), + item_name(pstrItemName), + delimiter(pstrDelimiter); + HRESULT ret; TSRMLS_FETCH(); - info.pstrCode = pstrCode; - info.pstrItemName = pstrItemName; - info.punkContext = punkContext; - info.pstrDelimiter = pstrDelimiter; - info.dwSourceContextCookie = dwSourceContextCookie; - info.ulStartingLineNumber = ulStartingLineNumber; - info.dwFlags = dwFlags; - info.pvarResult = pvarResult; - info.pexcepinfo = pexcepinfo; - - return SEND_THREAD_MESSAGE(this, PHPSE_PARSE_SCRIPT, 0, (LPARAM)&info TSRMLS_CC); + trace("pstrCode=%p pstrItemName=%p punkContext=%p pstrDelimiter=%p pvarResult=%p pexcepinfo=%p\n", + pstrCode, pstrItemName, punkContext, pstrDelimiter, pvarResult, pexcepinfo); + + if (tsrm_thread_id() != m_enginethread) { + return marshal_call(this, APHP_ParseScriptText, 9, + pstrCode, pstrItemName, punkContext, pstrDelimiter, + dwSourceContextCookie, ulStartingLineNumber, + dwFlags, pvarResult, pexcepinfo); + } + + trace("%08x: ParseScriptText:\n state=%s\ncode=%.*s\n item=%s\n delim=%s\n line=%d\n", + this, scriptstate_to_string(m_scriptstate), + code.m_ansi_strlen, + code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(), + ulStartingLineNumber); + + code_frag *frag = compile_code_fragment( + FRAG_MAIN, + dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL, + pstrCode, + ulStartingLineNumber, + pexcepinfo, + this + TSRMLS_CC); + + doexec = (dwFlags & SCRIPTTEXT_ISEXPRESSION) || + m_scriptstate == SCRIPTSTATE_STARTED || + m_scriptstate == SCRIPTSTATE_CONNECTED || + m_scriptstate == SCRIPTSTATE_DISCONNECTED; + + if (frag) { + frag->persistent = (dwFlags & SCRIPTTEXT_ISPERSISTENT); + ret = S_OK; + + if (dwFlags & SCRIPTTEXT_ISEXPRESSION) { + if (m_scriptstate == SCRIPTSTATE_INITIALIZED) { + /* not allowed to execute code in this state */ + ret = E_UNEXPECTED; + doexec = 0; + } + } + + if (doexec) { + /* execute the code as an expression */ + if (!execute_code_fragment(frag, pvarResult, pexcepinfo TSRMLS_CC)) + ret = DISP_E_EXCEPTION; + } + + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + } else { + ret = DISP_E_EXCEPTION; + } + + if (pvarResult) { + VariantInit(pvarResult); + } + + + return ret; } STDMETHODIMP TPHPScriptingEngine::ParseProcedureText( @@ -1822,37 +1768,69 @@ STDMETHODIMP TPHPScriptingEngine::ParseProcedureText( /* [in] */ DWORD dwFlags, /* [out] */ IDispatch **ppdisp) { - struct php_active_script_parse_proc_info info; +trace("ParseProcedureText\n"); + ENGINE_THREAD_ONLY(IActiveScriptParseProcedure, ParseProcedureText); + /* This is the IActiveScriptParseProcedure implementation. + * IE uses this to request for an IDispatch that it will invoke in + * response to some event, and tells us the code that it wants to + * run. + * We compile the code and pass it back a dispatch object. + * The object will then serialize access to the engine thread and + * execute the opcodes */ + TWideString + formal_params(pstrFormalParams), + procedure_name(pstrProcedureName), + item_name(pstrItemName), + delimiter(pstrDelimiter); HRESULT ret; TSRMLS_FETCH(); - info.pstrCode = pstrCode; - info.pstrFormalParams = pstrFormalParams; - info.pstrProcedureName = pstrProcedureName; - info.pstrItemName = pstrItemName; - info.punkContext = punkContext; - info.pstrDelimiter = pstrDelimiter; - info.dwSourceContextCookie = dwSourceContextCookie; - info.ulStartingLineNumber = ulStartingLineNumber; - info.dwFlags = dwFlags; + + trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n", + this, scriptstate_to_string(m_scriptstate), + formal_params.ansi_string(), procedure_name.ansi_string(), + item_name.safe_ansi_string(), delimiter.safe_ansi_string(), + ulStartingLineNumber); + + code_frag *frag = compile_code_fragment( + FRAG_PROCEDURE, + NULL, + pstrCode, + ulStartingLineNumber, + NULL, + this + TSRMLS_CC); + + if (frag) { + + frag->persistent = (dwFlags & SCRIPTTEXT_ISPERSISTENT); + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + + ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; + + disp->m_frag = frag; + disp->m_procflags = dwFlags; + disp->m_engine = this; + frag->ptr = disp; + + *ppdisp = (IDispatch*)disp; + } else { + ret = DISP_E_EXCEPTION; + } - ret = SEND_THREAD_MESSAGE(this, PHPSE_PARSE_PROC, 0, (LPARAM)&info TSRMLS_CC); - if (ret == S_OK) - ret = GIT_get(info.dispcookie, IID_IDispatch, (void**)ppdisp); - trace("ParseProc: ret=%08x disp=%08x\n", ret, *ppdisp); return ret; } -#if ACTIVEPHP_OBJECT_SAFETY STDMETHODIMP TPHPScriptingEngine::GetInterfaceSafetyOptions( /* [in] */ REFIID riid, // Interface that we want options for /* [out] */ DWORD *pdwSupportedOptions, // Options meaningful on this interface /* [out] */ DWORD *pdwEnabledOptions) // current option values on this interface { /* PHP isn't really safe for untrusted anything */ - *pdwSupportedOptions = 0; + trace("GetInterfaceSafetyOptions called\n"); + *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA|INTERFACESAFE_FOR_UNTRUSTED_CALLER; *pdwEnabledOptions = 0; return S_OK; } @@ -1863,22 +1841,49 @@ STDMETHODIMP TPHPScriptingEngine::SetInterfaceSafetyOptions( /* [in] */ DWORD dwEnabledOptions) // New option values { /* PHP isn't really safe for untrusted anything */ + trace("SetInterfaceSafetyOptions mask=%08x enabled=%08x\n", dwOptionSetMask, dwEnabledOptions); if (dwEnabledOptions == 0) { return S_OK; } + trace("can't set those options; we're not safe enough\n"); return E_FAIL; } -#endif - -extern "C" -void activescript_error_func(int type, const char *error_msg, ...) + +#if 0 +STDMETHODIMP TPHPScriptingEngine::GetUnmarshalClass( + REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, CLSID *pCid) { - TSRMLS_FETCH(); + return E_NOTIMPL; +} - TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); +STDMETHODIMP TPHPScriptingEngine::GetMarshalSizeMax( + REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, ULONG *pSize) +{ + return E_NOTIMPL; +} - +STDMETHODIMP TPHPScriptingEngine::MarshalInterface( + IStream *pStm, REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshflags) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPScriptingEngine::UnmarshalInterface( + IStream *pStm, REFIID riid, void **ppv) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPScriptingEngine::ReleaseMarshalData(IStream *pStm) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPScriptingEngine::DisconnectObject(DWORD dwReserved) +{ + return E_NOTIMPL; } +#endif class TActiveScriptError: public IActiveScriptError @@ -1929,9 +1934,12 @@ public: /* [out] */ ULONG *pulLineNumber, /* [out] */ LONG *plCharacterPosition) { - *pdwSourceContext = 0; - *pulLineNumber = m_lineno; - *plCharacterPosition = 0; + if (pdwSourceContext) + *pdwSourceContext = 0; + if (pulLineNumber) + *pulLineNumber = m_lineno; + if (plCharacterPosition) + *plCharacterPosition = 0; return S_OK; } @@ -1971,10 +1979,19 @@ void activescript_error_handler(int type, const char *error_filename, int buflen; TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); - buflen = vspprintf(&buf, PG(log_errors_max_len), format, args); - trace("%08x: Error: %s\n", engine, buf); - - /* if it's a fatal error, report it using IActiveScriptError. */ + /* ugly way to detect an exception for an eval'd fragment */ + if (type == E_ERROR && EG(exception) && strstr("Exception thrown without a stack frame", format)) { + zend_exception_error(EG(exception) TSRMLS_CC); + /* NOTREACHED -- recursive E_ERROR */ + } else { + buflen = vspprintf(&buf, PG(log_errors_max_len), format, args); + trace("%08x: PHP Error: %s\n", engine, buf); + } + + TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf); + trace("raising error object!\n"); + if (engine->m_pass) + engine->m_pass->OnScriptError(eobj); switch(type) { case E_ERROR: @@ -1982,16 +1999,8 @@ void activescript_error_handler(int type, const char *error_filename, case E_COMPILE_ERROR: case E_USER_ERROR: case E_PARSE: - /* trigger an error in the host */ - TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf); - trace("raising error object!\n"); - if (engine->m_pass_eng) - engine->m_pass_eng->OnScriptError(eobj); - /* now throw the exception to abort execution */ - if (engine->m_err_trap) - longjmp(*engine->m_err_trap, 1); - + zend_bailout(); break; } efree(buf); -- cgit v1.2.1