michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JavaScript Debugging support - 'High Level' functions michael@0: */ michael@0: michael@0: #include "jsd.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: using mozilla::AutoSafeJSContext; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: /* XXX not 'static' because of old Mac CodeWarrior bug */ michael@0: JSCList _jsd_context_list = JS_INIT_STATIC_CLIST(&_jsd_context_list); michael@0: michael@0: /* these are used to connect JSD_SetUserCallbacks() with JSD_DebuggerOn() */ michael@0: static JSD_UserCallbacks _callbacks; michael@0: static void* _user = nullptr; michael@0: static JSRuntime* _jsrt = nullptr; michael@0: michael@0: #ifdef JSD_HAS_DANGEROUS_THREAD michael@0: static void* _dangerousThread = nullptr; michael@0: #endif michael@0: michael@0: #ifdef JSD_THREADSAFE michael@0: JSDStaticLock* _jsd_global_lock = nullptr; michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: void JSD_ASSERT_VALID_CONTEXT(JSDContext* jsdc) michael@0: { michael@0: MOZ_ASSERT(jsdc->inited); michael@0: MOZ_ASSERT(jsdc->jsrt); michael@0: MOZ_ASSERT(jsdc->glob); michael@0: } michael@0: #endif michael@0: michael@0: /***************************************************************************/ michael@0: /* xpconnect related utility functions implemented in jsd_xpc.cpp */ michael@0: michael@0: extern void michael@0: global_finalize(JSFreeOp* fop, JSObject* obj); michael@0: michael@0: extern JSObject* michael@0: CreateJSDGlobal(JSContext *cx, const JSClass *clasp); michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: michael@0: static const JSClass global_class = { michael@0: "JSDGlobal", JSCLASS_GLOBAL_FLAGS | michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, global_finalize, michael@0: nullptr, nullptr, nullptr, michael@0: JS_GlobalObjectTraceHook michael@0: }; michael@0: michael@0: static bool michael@0: _validateUserCallbacks(JSD_UserCallbacks* callbacks) michael@0: { michael@0: return !callbacks || michael@0: (callbacks->size && callbacks->size <= sizeof(JSD_UserCallbacks)); michael@0: } michael@0: michael@0: static JSDContext* michael@0: _newJSDContext(JSRuntime* jsrt, michael@0: JSD_UserCallbacks* callbacks, michael@0: void* user, michael@0: JSObject* scopeobj) michael@0: { michael@0: JSDContext* jsdc = nullptr; michael@0: bool ok = true; michael@0: AutoSafeJSContext cx; michael@0: michael@0: if( ! jsrt ) michael@0: return nullptr; michael@0: michael@0: if( ! _validateUserCallbacks(callbacks) ) michael@0: return nullptr; michael@0: michael@0: jsdc = (JSDContext*) calloc(1, sizeof(JSDContext)); michael@0: if( ! jsdc ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: if( ! JSD_INIT_LOCKS(jsdc) ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: JS_INIT_CLIST(&jsdc->links); michael@0: michael@0: jsdc->jsrt = jsrt; michael@0: michael@0: if( callbacks ) michael@0: memcpy(&jsdc->userCallbacks, callbacks, callbacks->size); michael@0: michael@0: jsdc->user = user; michael@0: michael@0: #ifdef JSD_HAS_DANGEROUS_THREAD michael@0: jsdc->dangerousThread = _dangerousThread; michael@0: #endif michael@0: michael@0: JS_INIT_CLIST(&jsdc->threadsStates); michael@0: JS_INIT_CLIST(&jsdc->sources); michael@0: JS_INIT_CLIST(&jsdc->removedSources); michael@0: michael@0: jsdc->sourceAlterCount = 1; michael@0: michael@0: if( ! jsd_CreateAtomTable(jsdc) ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: if( ! jsd_InitObjectManager(jsdc) ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: if( ! jsd_InitScriptManager(jsdc) ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: { michael@0: JS::RootedObject global(cx, CreateJSDGlobal(cx, &global_class)); michael@0: if( ! global ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: jsdc->glob = global; michael@0: michael@0: JSAutoCompartment ac(cx, jsdc->glob); michael@0: ok = JS::AddNamedObjectRoot(cx, &jsdc->glob, "JSD context global") && michael@0: JS_InitStandardClasses(cx, global); michael@0: } michael@0: if( ! ok ) michael@0: goto label_newJSDContext_failure; michael@0: michael@0: jsdc->data = nullptr; michael@0: jsdc->inited = true; michael@0: michael@0: JSD_LOCK(); michael@0: JS_INSERT_LINK(&jsdc->links, &_jsd_context_list); michael@0: JSD_UNLOCK(); michael@0: michael@0: return jsdc; michael@0: michael@0: label_newJSDContext_failure: michael@0: if( jsdc ) { michael@0: if ( jsdc->glob ) michael@0: JS::RemoveObjectRootRT(JS_GetRuntime(cx), &jsdc->glob); michael@0: jsd_DestroyObjectManager(jsdc); michael@0: jsd_DestroyAtomTable(jsdc); michael@0: free(jsdc); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static void michael@0: _destroyJSDContext(JSDContext* jsdc) michael@0: { michael@0: JSD_ASSERT_VALID_CONTEXT(jsdc); michael@0: michael@0: JSD_LOCK(); michael@0: JS_REMOVE_LINK(&jsdc->links); michael@0: JSD_UNLOCK(); michael@0: michael@0: if ( jsdc->glob ) { michael@0: JS::RemoveObjectRootRT(jsdc->jsrt, &jsdc->glob); michael@0: } michael@0: jsd_DestroyObjectManager(jsdc); michael@0: jsd_DestroyAtomTable(jsdc); michael@0: michael@0: jsdc->inited = false; michael@0: michael@0: /* michael@0: * We should free jsdc here, but we let it leak in case there are any michael@0: * asynchronous hooks calling into the system using it as a handle michael@0: * michael@0: * XXX we also leak the locks michael@0: */ michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: JSDContext* michael@0: jsd_DebuggerOnForUser(JSRuntime* jsrt, michael@0: JSD_UserCallbacks* callbacks, michael@0: void* user, michael@0: JSObject* scopeobj) michael@0: { michael@0: JSDContext* jsdc; michael@0: michael@0: jsdc = _newJSDContext(jsrt, callbacks, user, scopeobj); michael@0: if( ! jsdc ) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Set hooks here. The new/destroy script hooks are on even when michael@0: * the debugger is paused. The destroy hook so we'll clean up michael@0: * internal data structures when scripts are destroyed, and the michael@0: * newscript hook for backwards compatibility for now. We'd like michael@0: * to stop doing that. michael@0: */ michael@0: JS_SetNewScriptHookProc(jsdc->jsrt, jsd_NewScriptHookProc, jsdc); michael@0: JS_SetDestroyScriptHookProc(jsdc->jsrt, jsd_DestroyScriptHookProc, jsdc); michael@0: jsd_DebuggerUnpause(jsdc); michael@0: michael@0: if( jsdc->userCallbacks.setContext ) michael@0: jsdc->userCallbacks.setContext(jsdc, jsdc->user); michael@0: return jsdc; michael@0: } michael@0: michael@0: JSDContext* michael@0: jsd_DebuggerOn(void) michael@0: { michael@0: MOZ_ASSERT(_jsrt); michael@0: MOZ_ASSERT(_validateUserCallbacks(&_callbacks)); michael@0: return jsd_DebuggerOnForUser(_jsrt, &_callbacks, _user, nullptr); michael@0: } michael@0: michael@0: void michael@0: jsd_DebuggerOff(JSDContext* jsdc) michael@0: { michael@0: jsd_DebuggerPause(jsdc, true); michael@0: /* clear hooks here */ michael@0: JS_SetNewScriptHookProc(jsdc->jsrt, nullptr, nullptr); michael@0: JS_SetDestroyScriptHookProc(jsdc->jsrt, nullptr, nullptr); michael@0: michael@0: /* clean up */ michael@0: JSD_LockScriptSubsystem(jsdc); michael@0: jsd_DestroyScriptManager(jsdc); michael@0: JSD_UnlockScriptSubsystem(jsdc); michael@0: jsd_DestroyAllSources(jsdc); michael@0: michael@0: _destroyJSDContext(jsdc); michael@0: michael@0: if( jsdc->userCallbacks.setContext ) michael@0: jsdc->userCallbacks.setContext(nullptr, jsdc->user); michael@0: } michael@0: michael@0: void michael@0: jsd_DebuggerPause(JSDContext* jsdc, bool forceAllHooksOff) michael@0: { michael@0: JS_SetDebuggerHandler(jsdc->jsrt, nullptr, nullptr); michael@0: if (forceAllHooksOff || !(jsdc->flags & JSD_COLLECT_PROFILE_DATA)) { michael@0: JS_SetExecuteHook(jsdc->jsrt, nullptr, nullptr); michael@0: JS_SetCallHook(jsdc->jsrt, nullptr, nullptr); michael@0: } michael@0: JS_SetThrowHook(jsdc->jsrt, nullptr, nullptr); michael@0: JS_SetDebugErrorHook(jsdc->jsrt, nullptr, nullptr); michael@0: } michael@0: michael@0: static bool michael@0: jsd_DebugErrorHook(JSContext *cx, const char *message, michael@0: JSErrorReport *report, void *closure); michael@0: michael@0: void michael@0: jsd_DebuggerUnpause(JSDContext* jsdc) michael@0: { michael@0: JS_SetDebuggerHandler(jsdc->jsrt, jsd_DebuggerHandler, jsdc); michael@0: JS_SetExecuteHook(jsdc->jsrt, jsd_TopLevelCallHook, jsdc); michael@0: JS_SetCallHook(jsdc->jsrt, jsd_FunctionCallHook, jsdc); michael@0: JS_SetThrowHook(jsdc->jsrt, jsd_ThrowHandler, jsdc); michael@0: JS_SetDebugErrorHook(jsdc->jsrt, jsd_DebugErrorHook, jsdc); michael@0: } michael@0: michael@0: void michael@0: jsd_SetUserCallbacks(JSRuntime* jsrt, JSD_UserCallbacks* callbacks, void* user) michael@0: { michael@0: _jsrt = jsrt; michael@0: _user = user; michael@0: michael@0: #ifdef JSD_HAS_DANGEROUS_THREAD michael@0: _dangerousThread = JSD_CURRENT_THREAD(); michael@0: #endif michael@0: michael@0: if( callbacks ) michael@0: memcpy(&_callbacks, callbacks, sizeof(JSD_UserCallbacks)); michael@0: else michael@0: memset(&_callbacks, 0 , sizeof(JSD_UserCallbacks)); michael@0: } michael@0: michael@0: void* michael@0: jsd_SetContextPrivate(JSDContext* jsdc, void *data) michael@0: { michael@0: jsdc->data = data; michael@0: return data; michael@0: } michael@0: michael@0: void* michael@0: jsd_GetContextPrivate(JSDContext* jsdc) michael@0: { michael@0: return jsdc->data; michael@0: } michael@0: michael@0: void michael@0: jsd_ClearAllProfileData(JSDContext* jsdc) michael@0: { michael@0: JSDScript *current; michael@0: michael@0: JSD_LOCK_SCRIPTS(jsdc); michael@0: current = (JSDScript *)jsdc->scripts.next; michael@0: while (current != (JSDScript *)&jsdc->scripts) michael@0: { michael@0: jsd_ClearScriptProfileData(jsdc, current); michael@0: current = (JSDScript *)current->links.next; michael@0: } michael@0: michael@0: JSD_UNLOCK_SCRIPTS(jsdc); michael@0: } michael@0: michael@0: JSDContext* michael@0: jsd_JSDContextForJSContext(JSContext* context) michael@0: { michael@0: JSDContext* iter; michael@0: JSDContext* jsdc = nullptr; michael@0: JSRuntime* runtime = JS_GetRuntime(context); michael@0: michael@0: JSD_LOCK(); michael@0: for( iter = (JSDContext*)_jsd_context_list.next; michael@0: iter != (JSDContext*)&_jsd_context_list; michael@0: iter = (JSDContext*)iter->links.next ) michael@0: { michael@0: if( runtime == iter->jsrt ) michael@0: { michael@0: jsdc = iter; michael@0: break; michael@0: } michael@0: } michael@0: JSD_UNLOCK(); michael@0: return jsdc; michael@0: } michael@0: michael@0: static bool michael@0: jsd_DebugErrorHook(JSContext *cx, const char *message, michael@0: JSErrorReport *report, void *closure) michael@0: { michael@0: JSDContext* jsdc = (JSDContext*) closure; michael@0: JSD_ErrorReporter errorReporter; michael@0: void* errorReporterData; michael@0: michael@0: if( ! jsdc ) michael@0: { michael@0: MOZ_ASSERT(0); michael@0: return true; michael@0: } michael@0: if( JSD_IS_DANGEROUS_THREAD(jsdc) ) michael@0: return true; michael@0: michael@0: /* local in case hook gets cleared on another thread */ michael@0: JSD_LOCK(); michael@0: errorReporter = jsdc->errorReporter; michael@0: errorReporterData = jsdc->errorReporterData; michael@0: JSD_UNLOCK(); michael@0: michael@0: if(!errorReporter) michael@0: return true; michael@0: michael@0: switch(errorReporter(jsdc, cx, message, report, errorReporterData)) michael@0: { michael@0: case JSD_ERROR_REPORTER_PASS_ALONG: michael@0: return true; michael@0: case JSD_ERROR_REPORTER_RETURN: michael@0: return false; michael@0: case JSD_ERROR_REPORTER_DEBUG: michael@0: { michael@0: JS::RootedValue rval(cx); michael@0: JSD_ExecutionHookProc hook; michael@0: void* hookData; michael@0: michael@0: /* local in case hook gets cleared on another thread */ michael@0: JSD_LOCK(); michael@0: hook = jsdc->debugBreakHook; michael@0: hookData = jsdc->debugBreakHookData; michael@0: JSD_UNLOCK(); michael@0: michael@0: jsd_CallExecutionHook(jsdc, cx, JSD_HOOK_DEBUG_REQUESTED, michael@0: hook, hookData, rval.address()); michael@0: /* XXX Should make this dependent on ExecutionHook retval */ michael@0: return true; michael@0: } michael@0: case JSD_ERROR_REPORTER_CLEAR_RETURN: michael@0: if(report && JSREPORT_IS_EXCEPTION(report->flags)) michael@0: JS_ClearPendingException(cx); michael@0: return false; michael@0: default: michael@0: MOZ_ASSERT(0); michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: jsd_SetErrorReporter(JSDContext* jsdc, michael@0: JSD_ErrorReporter reporter, michael@0: void* callerdata) michael@0: { michael@0: JSD_LOCK(); michael@0: jsdc->errorReporter = reporter; michael@0: jsdc->errorReporterData = callerdata; michael@0: JSD_UNLOCK(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: jsd_GetErrorReporter(JSDContext* jsdc, michael@0: JSD_ErrorReporter* reporter, michael@0: void** callerdata) michael@0: { michael@0: JSD_LOCK(); michael@0: if( reporter ) michael@0: *reporter = jsdc->errorReporter; michael@0: if( callerdata ) michael@0: *callerdata = jsdc->errorReporterData; michael@0: JSD_UNLOCK(); michael@0: return true; michael@0: }