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 - Call stack support michael@0: */ michael@0: michael@0: #include "jsd.h" michael@0: #include "jsfriendapi.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: using mozilla::AutoPushJSContext; michael@0: michael@0: #ifdef DEBUG michael@0: void JSD_ASSERT_VALID_THREAD_STATE(JSDThreadState* jsdthreadstate) michael@0: { michael@0: MOZ_ASSERT(jsdthreadstate); michael@0: MOZ_ASSERT(jsdthreadstate->stackDepth > 0); michael@0: } michael@0: michael@0: void JSD_ASSERT_VALID_STACK_FRAME(JSDStackFrameInfo* jsdframe) michael@0: { michael@0: MOZ_ASSERT(jsdframe); michael@0: MOZ_ASSERT(jsdframe->jsdthreadstate); michael@0: } michael@0: #endif michael@0: michael@0: static JSDStackFrameInfo* michael@0: _addNewFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSScript* script, michael@0: uintptr_t pc, michael@0: bool isConstructing, michael@0: JSAbstractFramePtr frame) michael@0: { michael@0: JSDStackFrameInfo* jsdframe; michael@0: JSDScript* jsdscript = nullptr; michael@0: michael@0: JSD_LOCK_SCRIPTS(jsdc); michael@0: jsdscript = jsd_FindJSDScript(jsdc, script); michael@0: JSD_UNLOCK_SCRIPTS(jsdc); michael@0: if (!jsdscript || (jsdc->flags & JSD_HIDE_DISABLED_FRAMES && michael@0: !JSD_IS_DEBUG_ENABLED(jsdc, jsdscript))) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!JSD_IS_DEBUG_ENABLED(jsdc, jsdscript)) michael@0: jsdthreadstate->flags |= TS_HAS_DISABLED_FRAME; michael@0: michael@0: jsdframe = (JSDStackFrameInfo*) calloc(1, sizeof(JSDStackFrameInfo)); michael@0: if( ! jsdframe ) michael@0: return nullptr; michael@0: michael@0: jsdframe->jsdthreadstate = jsdthreadstate; michael@0: jsdframe->jsdscript = jsdscript; michael@0: jsdframe->isConstructing = isConstructing; michael@0: jsdframe->pc = pc; michael@0: jsdframe->frame = frame; michael@0: michael@0: JS_APPEND_LINK(&jsdframe->links, &jsdthreadstate->stack); michael@0: jsdthreadstate->stackDepth++; michael@0: michael@0: return jsdframe; michael@0: } michael@0: michael@0: static void michael@0: _destroyFrame(JSDStackFrameInfo* jsdframe) michael@0: { michael@0: /* kill any alloc'd objects in frame here... */ michael@0: michael@0: if( jsdframe ) michael@0: free(jsdframe); michael@0: } michael@0: michael@0: JSDThreadState* michael@0: jsd_NewThreadState(JSDContext* jsdc, JSContext *cx ) michael@0: { michael@0: JSDThreadState* jsdthreadstate; michael@0: michael@0: jsdthreadstate = (JSDThreadState*)calloc(1, sizeof(JSDThreadState)); michael@0: if( ! jsdthreadstate ) michael@0: return nullptr; michael@0: michael@0: jsdthreadstate->context = cx; michael@0: jsdthreadstate->thread = JSD_CURRENT_THREAD(); michael@0: JS_INIT_CLIST(&jsdthreadstate->stack); michael@0: jsdthreadstate->stackDepth = 0; michael@0: michael@0: JS_BeginRequest(jsdthreadstate->context); michael@0: michael@0: JSBrokenFrameIterator iter(cx); michael@0: while(!iter.done()) michael@0: { michael@0: JSAbstractFramePtr frame = iter.abstractFramePtr(); michael@0: JS::RootedScript script(cx, frame.script()); michael@0: uintptr_t pc = (uintptr_t)frame.pc(); michael@0: JS::RootedValue dummyThis(cx); michael@0: michael@0: /* michael@0: * don't construct a JSDStackFrame for dummy frames (those without a michael@0: * |this| object, or native frames, if JSD_INCLUDE_NATIVE_FRAMES michael@0: * isn't set. michael@0: */ michael@0: if (frame.getThisValue(cx, &dummyThis)) michael@0: { michael@0: bool isConstructing = iter.isConstructing(); michael@0: JSDStackFrameInfo *frameInfo = _addNewFrame( jsdc, jsdthreadstate, script, pc, isConstructing, frame ); michael@0: michael@0: if ((jsdthreadstate->stackDepth == 0 && !frameInfo) || michael@0: (jsdthreadstate->stackDepth == 1 && frameInfo && michael@0: frameInfo->jsdscript && !JSD_IS_DEBUG_ENABLED(jsdc, frameInfo->jsdscript))) michael@0: { michael@0: /* michael@0: * if we failed to create the first frame, or the top frame michael@0: * is not enabled for debugging, fail the entire thread state. michael@0: */ michael@0: JS_INIT_CLIST(&jsdthreadstate->links); michael@0: JS_EndRequest(jsdthreadstate->context); michael@0: jsd_DestroyThreadState(jsdc, jsdthreadstate); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: ++iter; michael@0: } michael@0: JS_EndRequest(jsdthreadstate->context); michael@0: michael@0: if (jsdthreadstate->stackDepth == 0) michael@0: { michael@0: free(jsdthreadstate); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: JS_APPEND_LINK(&jsdthreadstate->links, &jsdc->threadsStates); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return jsdthreadstate; michael@0: } michael@0: michael@0: void michael@0: jsd_DestroyThreadState(JSDContext* jsdc, JSDThreadState* jsdthreadstate) michael@0: { michael@0: JSDStackFrameInfo* jsdframe; michael@0: JSCList* list; michael@0: michael@0: MOZ_ASSERT(jsdthreadstate); michael@0: MOZ_ASSERT(JSD_CURRENT_THREAD() == jsdthreadstate->thread); michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: JS_REMOVE_LINK(&jsdthreadstate->links); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: list = &jsdthreadstate->stack; michael@0: while( (JSDStackFrameInfo*)list != (jsdframe = (JSDStackFrameInfo*)list->next) ) michael@0: { michael@0: JS_REMOVE_LINK(&jsdframe->links); michael@0: _destroyFrame(jsdframe); michael@0: } michael@0: free(jsdthreadstate); michael@0: } michael@0: michael@0: unsigned michael@0: jsd_GetCountOfStackFrames(JSDContext* jsdc, JSDThreadState* jsdthreadstate) michael@0: { michael@0: unsigned count = 0; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidThreadState(jsdc, jsdthreadstate) ) michael@0: count = jsdthreadstate->stackDepth; michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: JSDStackFrameInfo* michael@0: jsd_GetStackFrame(JSDContext* jsdc, JSDThreadState* jsdthreadstate) michael@0: { michael@0: JSDStackFrameInfo* jsdframe = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidThreadState(jsdc, jsdthreadstate) ) michael@0: jsdframe = (JSDStackFrameInfo*) JS_LIST_HEAD(&jsdthreadstate->stack); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return jsdframe; michael@0: } michael@0: michael@0: JSContext * michael@0: jsd_GetJSContext (JSDContext* jsdc, JSDThreadState* jsdthreadstate) michael@0: { michael@0: JSContext* cx = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: if( jsd_IsValidThreadState(jsdc, jsdthreadstate) ) michael@0: cx = jsdthreadstate->context; michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return cx; michael@0: } michael@0: michael@0: JSDStackFrameInfo* michael@0: jsd_GetCallingStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: JSDStackFrameInfo* nextjsdframe = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: if( JS_LIST_HEAD(&jsdframe->links) != &jsdframe->jsdthreadstate->stack ) michael@0: nextjsdframe = (JSDStackFrameInfo*) JS_LIST_HEAD(&jsdframe->links); michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return nextjsdframe; michael@0: } michael@0: michael@0: JSDScript* michael@0: jsd_GetScriptForStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: JSDScript* jsdscript = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: jsdscript = jsdframe->jsdscript; michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return jsdscript; michael@0: } michael@0: michael@0: uintptr_t michael@0: jsd_GetPCForStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: uintptr_t pc = 0; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: pc = jsdframe->pc; michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return pc; michael@0: } michael@0: michael@0: JSDValue* michael@0: jsd_GetCallObjectForStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: JSObject* obj; michael@0: JSDValue* jsdval = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: { michael@0: obj = jsdframe->frame.callObject(jsdthreadstate->context); michael@0: if(obj) michael@0: jsdval = JSD_NewValue(jsdc, OBJECT_TO_JSVAL(obj)); michael@0: } michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return jsdval; michael@0: } michael@0: michael@0: JSDValue* michael@0: jsd_GetScopeChainForStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: JS::RootedObject obj(jsdthreadstate->context); michael@0: JSDValue* jsdval = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: { michael@0: JS_BeginRequest(jsdthreadstate->context); michael@0: obj = jsdframe->frame.scopeChain(jsdthreadstate->context); michael@0: JS_EndRequest(jsdthreadstate->context); michael@0: if(obj) michael@0: jsdval = JSD_NewValue(jsdc, OBJECT_TO_JSVAL(obj)); michael@0: } michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: return jsdval; michael@0: } michael@0: michael@0: JSDValue* michael@0: jsd_GetThisForStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: JSDValue* jsdval = nullptr; michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: { michael@0: bool ok; michael@0: JS::RootedValue thisval(jsdthreadstate->context); michael@0: JS_BeginRequest(jsdthreadstate->context); michael@0: ok = jsdframe->frame.getThisValue(jsdthreadstate->context, &thisval); michael@0: JS_EndRequest(jsdthreadstate->context); michael@0: if(ok) michael@0: jsdval = JSD_NewValue(jsdc, thisval); michael@0: } michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: return jsdval; michael@0: } michael@0: michael@0: JSString* michael@0: jsd_GetIdForStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: JSString *rv = nullptr; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: { michael@0: JSFunction *fun = jsdframe->frame.maybeFun(); michael@0: if( fun ) michael@0: { michael@0: rv = JS_GetFunctionId (fun); michael@0: michael@0: /* michael@0: * For compatibility we return "anonymous", not an empty string michael@0: * here. michael@0: */ michael@0: if( !rv ) michael@0: rv = JS_GetAnonymousString(jsdc->jsrt); michael@0: } michael@0: } michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: jsd_IsStackFrameDebugger(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: bool rv = true; michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: { michael@0: rv = jsdframe->frame.isDebuggerFrame(); michael@0: } michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: jsd_IsStackFrameConstructing(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: bool rv = true; michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: michael@0: if( jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe) ) michael@0: { michael@0: rv = jsdframe->isConstructing; michael@0: } michael@0: michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: jsd_EvaluateUCScriptInStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe, michael@0: const jschar *bytes, unsigned length, michael@0: const char *filename, unsigned lineno, michael@0: bool eatExceptions, JS::MutableHandleValue rval) michael@0: { michael@0: bool retval; michael@0: bool valid; michael@0: JSExceptionState* exceptionState = nullptr; michael@0: michael@0: MOZ_ASSERT(JSD_CURRENT_THREAD() == jsdthreadstate->thread); michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: valid = jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: if( ! valid ) michael@0: return false; michael@0: michael@0: AutoPushJSContext cx(jsdthreadstate->context); michael@0: MOZ_ASSERT(cx); michael@0: michael@0: if (eatExceptions) michael@0: exceptionState = JS_SaveExceptionState(cx); michael@0: JS_ClearPendingException(cx); michael@0: jsd_StartingEvalUsingFilename(jsdc, filename); michael@0: retval = jsdframe->frame.evaluateUCInStackFrame(cx, bytes, length, filename, lineno, michael@0: rval); michael@0: jsd_FinishedEvalUsingFilename(jsdc, filename); michael@0: if (eatExceptions) michael@0: JS_RestoreExceptionState(cx, exceptionState); michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: bool michael@0: jsd_EvaluateScriptInStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe, michael@0: const char *bytes, unsigned length, michael@0: const char *filename, unsigned lineno, michael@0: bool eatExceptions, JS::MutableHandleValue rval) michael@0: { michael@0: bool retval; michael@0: bool valid; michael@0: JSExceptionState* exceptionState = nullptr; michael@0: michael@0: MOZ_ASSERT(JSD_CURRENT_THREAD() == jsdthreadstate->thread); michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: valid = jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: if (!valid) michael@0: return false; michael@0: michael@0: AutoPushJSContext cx(jsdthreadstate->context); michael@0: MOZ_ASSERT(cx); michael@0: michael@0: if (eatExceptions) michael@0: exceptionState = JS_SaveExceptionState(cx); michael@0: JS_ClearPendingException(cx); michael@0: jsd_StartingEvalUsingFilename(jsdc, filename); michael@0: retval = jsdframe->frame.evaluateInStackFrame(cx, bytes, length, filename, lineno, michael@0: rval); michael@0: jsd_FinishedEvalUsingFilename(jsdc, filename); michael@0: if (eatExceptions) michael@0: JS_RestoreExceptionState(cx, exceptionState); michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: JSString* michael@0: jsd_ValToStringInStackFrame(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe, michael@0: jsval val) michael@0: { michael@0: bool valid; michael@0: JSExceptionState* exceptionState; michael@0: JSContext *cx = jsdthreadstate->context; michael@0: michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: valid = jsd_IsValidFrameInThreadState(jsdc, jsdthreadstate, jsdframe); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: michael@0: if( ! valid ) michael@0: return nullptr; michael@0: michael@0: JS::RootedString retval(cx); michael@0: MOZ_ASSERT(cx); michael@0: JS::RootedValue v(cx, val); michael@0: { michael@0: AutoPushJSContext cx(jsdthreadstate->context); michael@0: exceptionState = JS_SaveExceptionState(cx); michael@0: retval = JS::ToString(cx, v); michael@0: JS_RestoreExceptionState(cx, exceptionState); michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: bool michael@0: jsd_IsValidThreadState(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate) michael@0: { michael@0: JSDThreadState *cur; michael@0: michael@0: MOZ_ASSERT( JSD_THREADSTATES_LOCKED(jsdc) ); michael@0: michael@0: for( cur = (JSDThreadState*)jsdc->threadsStates.next; michael@0: cur != (JSDThreadState*)&jsdc->threadsStates; michael@0: cur = (JSDThreadState*)cur->links.next ) michael@0: { michael@0: if( cur == jsdthreadstate ) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: jsd_IsValidFrameInThreadState(JSDContext* jsdc, michael@0: JSDThreadState* jsdthreadstate, michael@0: JSDStackFrameInfo* jsdframe) michael@0: { michael@0: MOZ_ASSERT(JSD_THREADSTATES_LOCKED(jsdc)); michael@0: michael@0: if( ! jsd_IsValidThreadState(jsdc, jsdthreadstate) ) michael@0: return false; michael@0: if( jsdframe->jsdthreadstate != jsdthreadstate ) michael@0: return false; michael@0: michael@0: JSD_ASSERT_VALID_THREAD_STATE(jsdthreadstate); michael@0: JSD_ASSERT_VALID_STACK_FRAME(jsdframe); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static JSContext* michael@0: _getContextForThreadState(JSDContext* jsdc, JSDThreadState* jsdthreadstate) michael@0: { michael@0: bool valid; michael@0: JSD_LOCK_THREADSTATES(jsdc); michael@0: valid = jsd_IsValidThreadState(jsdc, jsdthreadstate); michael@0: JSD_UNLOCK_THREADSTATES(jsdc); michael@0: if( valid ) michael@0: return jsdthreadstate->context; michael@0: return nullptr; michael@0: } michael@0: michael@0: JSDValue* michael@0: jsd_GetException(JSDContext* jsdc, JSDThreadState* jsdthreadstate) michael@0: { michael@0: JSContext* cx; michael@0: if(!(cx = _getContextForThreadState(jsdc, jsdthreadstate))) michael@0: return nullptr; michael@0: michael@0: JS::RootedValue val(cx); michael@0: if(JS_GetPendingException(cx, &val)) michael@0: return jsd_NewValue(jsdc, val); michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: jsd_SetException(JSDContext* jsdc, JSDThreadState* jsdthreadstate, michael@0: JSDValue* jsdval) michael@0: { michael@0: JSContext* cx; michael@0: michael@0: if(!(cx = _getContextForThreadState(jsdc, jsdthreadstate))) michael@0: return false; michael@0: michael@0: if(jsdval) { michael@0: JS::RootedValue exn(cx, JSD_GetValueWrappedJSVal(jsdc, jsdval)); michael@0: JS_SetPendingException(cx, exn); michael@0: } else { michael@0: JS_ClearPendingException(cx); michael@0: } michael@0: return true; michael@0: } michael@0: