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 - Stepping support michael@0: */ michael@0: michael@0: #include "jsd.h" michael@0: michael@0: /* michael@0: * #define JSD_TRACE 1 michael@0: */ michael@0: michael@0: #ifdef JSD_TRACE michael@0: michael@0: static char* michael@0: _indentSpaces(int i) michael@0: { michael@0: #define MAX_INDENT 63 michael@0: static char* p = nullptr; michael@0: if(!p) michael@0: { michael@0: p = calloc(1, MAX_INDENT+1); michael@0: if(!p) return ""; michael@0: memset(p, ' ', MAX_INDENT); michael@0: } michael@0: if(i > MAX_INDENT) return p; michael@0: return p + MAX_INDENT-i; michael@0: } michael@0: michael@0: static void michael@0: _interpreterTrace(JSDContext* jsdc, JSContext *cx, JSAbstractFramePtr frame, michael@0: bool isConstructing, bool before) michael@0: { michael@0: JSDScript* jsdscript = nullptr; michael@0: JSScript * script; michael@0: static indent = 0; michael@0: JSString* funName = nullptr; michael@0: michael@0: script = frame.script(); michael@0: if(script) michael@0: { michael@0: JSD_LOCK_SCRIPTS(jsdc); michael@0: jsdscript = jsd_FindOrCreateJSDScript(jsdc, cx, script, frame); michael@0: JSD_UNLOCK_SCRIPTS(jsdc); michael@0: if(jsdscript) michael@0: funName = JSD_GetScriptFunctionId(jsdc, jsdscript); michael@0: } michael@0: michael@0: if(before) michael@0: printf("%sentering ", _indentSpaces(indent++)); michael@0: else michael@0: printf("%sleaving ", _indentSpaces(--indent)); michael@0: michael@0: if (!funName) michael@0: printf("TOP_LEVEL"); michael@0: else michael@0: JS_FileEscapedString(stdout, funName, 0); michael@0: michael@0: if(before) michael@0: { michael@0: jsval thisVal; michael@0: michael@0: printf("%s this: ", isConstructing ? "constructing":""); michael@0: michael@0: if (JS_GetFrameThis(cx, frame, &thisVal)) michael@0: printf("0x%0llx", (uintptr_t) thisVal); michael@0: else michael@0: puts(""); michael@0: } michael@0: printf("\n"); michael@0: MOZ_ASSERT(indent >= 0); michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: _callHook(JSDContext *jsdc, JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, michael@0: bool before, unsigned type, JSD_CallHookProc hook, void *hookData) michael@0: { michael@0: JSDScript* jsdscript; michael@0: JSScript* jsscript; michael@0: bool hookresult = true; michael@0: michael@0: if (!jsdc || !jsdc->inited) michael@0: return false; michael@0: michael@0: if (!hook && !(jsdc->flags & JSD_COLLECT_PROFILE_DATA)) michael@0: { michael@0: /* no hook to call, no profile data needs to be collected, michael@0: * so there is nothing to do here. michael@0: */ michael@0: return hookresult; michael@0: } michael@0: michael@0: if (before && isConstructing) { michael@0: JS::RootedValue newObj(cx); michael@0: if (!frame.getThisValue(cx, &newObj)) michael@0: return false; michael@0: jsd_Constructing(jsdc, cx, JSVAL_TO_OBJECT(newObj), frame); michael@0: } michael@0: michael@0: jsscript = frame.script(); michael@0: if (jsscript) michael@0: { michael@0: JSD_LOCK_SCRIPTS(jsdc); michael@0: jsdscript = jsd_FindOrCreateJSDScript(jsdc, cx, jsscript, frame); michael@0: JSD_UNLOCK_SCRIPTS(jsdc); michael@0: michael@0: if (jsdscript) michael@0: { michael@0: if (JSD_IS_PROFILE_ENABLED(jsdc, jsdscript)) michael@0: { michael@0: JSDProfileData *pdata; michael@0: pdata = jsd_GetScriptProfileData (jsdc, jsdscript); michael@0: if (pdata) michael@0: { michael@0: if (before) michael@0: { michael@0: if (!pdata->lastCallStart) michael@0: { michael@0: int64_t now; michael@0: JSDProfileData *callerpdata; michael@0: michael@0: /* Get the time just the once, for consistency. */ michael@0: now = JS_Now(); michael@0: /* This contains a pointer to the profile data for michael@0: * the caller of this function. */ michael@0: callerpdata = jsdc->callingFunctionPData; michael@0: if (callerpdata) michael@0: { michael@0: int64_t ll_delta; michael@0: pdata->caller = callerpdata; michael@0: /* We need to 'stop' the timer for the caller. michael@0: * Use time since last return if appropriate. */ michael@0: ll_delta = jsdc->lastReturnTime michael@0: ? now - jsdc->lastReturnTime michael@0: : now - callerpdata->lastCallStart; michael@0: callerpdata->runningTime += ll_delta; michael@0: } michael@0: /* We're the new current function, and no return michael@0: * has happened yet. */ michael@0: jsdc->callingFunctionPData = pdata; michael@0: jsdc->lastReturnTime = 0; michael@0: /* This function has no running time (just been michael@0: * called!), and we'll need the call start time. */ michael@0: pdata->runningTime = 0; michael@0: pdata->lastCallStart = now; michael@0: } else { michael@0: if (++pdata->recurseDepth > pdata->maxRecurseDepth) michael@0: pdata->maxRecurseDepth = pdata->recurseDepth; michael@0: } michael@0: /* make sure we're called for the return too. */ michael@0: hookresult = true; michael@0: } else if (!pdata->recurseDepth && pdata->lastCallStart) { michael@0: int64_t now, ll_delta; michael@0: double delta; michael@0: now = JS_Now(); michael@0: ll_delta = now - pdata->lastCallStart; michael@0: delta = ll_delta; michael@0: delta /= 1000.0; michael@0: pdata->totalExecutionTime += delta; michael@0: /* minExecutionTime starts as 0, so we need to overwrite michael@0: * it on the first call always. */ michael@0: if ((0 == pdata->callCount) || michael@0: delta < pdata->minExecutionTime) michael@0: { michael@0: pdata->minExecutionTime = delta; michael@0: } michael@0: if (delta > pdata->maxExecutionTime) michael@0: pdata->maxExecutionTime = delta; michael@0: michael@0: /* If we last returned from a function (as opposed to michael@0: * having last entered this function), we need to inc. michael@0: * the running total by the time delta since the last michael@0: * return, and use the running total instead of the michael@0: * delta calculated above. */ michael@0: if (jsdc->lastReturnTime) michael@0: { michael@0: /* Add last chunk to running time, and use total michael@0: * running time as 'delta'. */ michael@0: ll_delta = now - jsdc->lastReturnTime; michael@0: pdata->runningTime += ll_delta; michael@0: delta = pdata->runningTime; michael@0: delta /= 1000.0; michael@0: } michael@0: michael@0: pdata->totalOwnExecutionTime += delta; michael@0: /* See minExecutionTime comment above. */ michael@0: if ((0 == pdata->callCount) || michael@0: delta < pdata->minOwnExecutionTime) michael@0: { michael@0: pdata->minOwnExecutionTime = delta; michael@0: } michael@0: if (delta > pdata->maxOwnExecutionTime) michael@0: pdata->maxOwnExecutionTime = delta; michael@0: michael@0: /* Current function is now our caller. */ michael@0: jsdc->callingFunctionPData = pdata->caller; michael@0: /* No hanging pointers, please. */ michael@0: pdata->caller = nullptr; michael@0: /* Mark the time we returned, and indicate this michael@0: * function is no longer running. */ michael@0: jsdc->lastReturnTime = now; michael@0: pdata->lastCallStart = 0; michael@0: ++pdata->callCount; michael@0: } else if (pdata->recurseDepth) { michael@0: --pdata->recurseDepth; michael@0: ++pdata->callCount; michael@0: } michael@0: } michael@0: if (hook) michael@0: jsd_CallCallHook (jsdc, cx, type, hook, hookData); michael@0: } else { michael@0: if (hook) michael@0: hookresult = michael@0: jsd_CallCallHook (jsdc, cx, type, hook, hookData); michael@0: else michael@0: hookresult = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef JSD_TRACE michael@0: _interpreterTrace(jsdc, cx, frame, isConstructing, before); michael@0: return true; michael@0: #else michael@0: return hookresult; michael@0: #endif michael@0: michael@0: } michael@0: michael@0: void * michael@0: jsd_FunctionCallHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, michael@0: bool before, bool *ok, void *closure) michael@0: { michael@0: JSDContext* jsdc; michael@0: JSD_CallHookProc hook; michael@0: void* hookData; michael@0: michael@0: jsdc = (JSDContext*) closure; michael@0: michael@0: /* local in case jsdc->functionHook gets cleared on another thread */ michael@0: JSD_LOCK(); michael@0: hook = jsdc->functionHook; michael@0: hookData = jsdc->functionHookData; michael@0: JSD_UNLOCK(); michael@0: michael@0: if (_callHook (jsdc, cx, frame, isConstructing, before, michael@0: (before) ? JSD_HOOK_FUNCTION_CALL : JSD_HOOK_FUNCTION_RETURN, michael@0: hook, hookData)) michael@0: { michael@0: return closure; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void * michael@0: jsd_TopLevelCallHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, michael@0: bool before, bool *ok, void *closure) michael@0: { michael@0: JSDContext* jsdc; michael@0: JSD_CallHookProc hook; michael@0: void* hookData; michael@0: michael@0: jsdc = (JSDContext*) closure; michael@0: michael@0: /* local in case jsdc->toplevelHook gets cleared on another thread */ michael@0: JSD_LOCK(); michael@0: hook = jsdc->toplevelHook; michael@0: hookData = jsdc->toplevelHookData; michael@0: JSD_UNLOCK(); michael@0: michael@0: if (_callHook (jsdc, cx, frame, isConstructing, before, michael@0: (before) ? JSD_HOOK_TOPLEVEL_START : JSD_HOOK_TOPLEVEL_END, michael@0: hook, hookData)) michael@0: { michael@0: return closure; michael@0: } michael@0: michael@0: return nullptr; michael@0: michael@0: }