/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jsdbgapi.h" #include "jslock.h" #include "jsd_xpc.h" #include "js/GCAPI.h" #include "nsIXPConnect.h" #include "mozilla/ModuleUtils.h" #include "nsIServiceManager.h" #include "nsIScriptGlobalObject.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsICategoryManager.h" #include "nsIJSRuntimeService.h" #include "nsIThreadInternal.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "nsMemory.h" #include "jsdebug.h" #include "nsReadableUtils.h" #include "nsCRT.h" #include "nsCycleCollectionParticipant.h" #include "mozilla/Attributes.h" /* XXX DOM dependency */ #include "nsIScriptContext.h" #include "SandboxPrivate.h" #include "nsJSPrincipals.h" #include "nsContentUtils.h" #include "nsCxPusher.h" /* * defining CAUTIOUS_SCRIPTHOOK makes jsds disable GC while calling out to the * script hook. This was a hack to avoid some js engine problems that should * be fixed now (see Mozilla bug 77636). */ #undef CAUTIOUS_SCRIPTHOOK #ifdef DEBUG_verbose # define DEBUG_COUNT(name, count) \ { if ((count % 10) == 0) printf (name ": %i\n", count); } # define DEBUG_CREATE(name, count) {count++; DEBUG_COUNT ("+++++ " name,count)} # define DEBUG_DESTROY(name, count) {count--; DEBUG_COUNT ("----- " name,count)} #else # define DEBUG_CREATE(name, count) # define DEBUG_DESTROY(name, count) #endif #define ASSERT_VALID_CONTEXT { if (!mCx) return NS_ERROR_NOT_AVAILABLE; } #define ASSERT_VALID_EPHEMERAL { if (!mValid) return NS_ERROR_NOT_AVAILABLE; } #define JSDSERVICE_CID \ { /* f1299dc2-1dd1-11b2-a347-ee6b7660e048 */ \ 0xf1299dc2, \ 0x1dd1, \ 0x11b2, \ {0xa3, 0x47, 0xee, 0x6b, 0x76, 0x60, 0xe0, 0x48} \ } #define JSDASO_CID \ { /* 2fd6b7f6-eb8c-4f32-ad26-113f2c02d0fe */ \ 0x2fd6b7f6, \ 0xeb8c, \ 0x4f32, \ {0xad, 0x26, 0x11, 0x3f, 0x2c, 0x02, 0xd0, 0xfe} \ } #define JSDS_MAJOR_VERSION 1 #define JSDS_MINOR_VERSION 2 #define NS_CATMAN_CTRID "@mozilla.org/categorymanager;1" #define NS_JSRT_CTRID "@mozilla.org/js/xpc/RuntimeService;1" #define AUTOREG_CATEGORY "xpcom-autoregistration" #define APPSTART_CATEGORY "app-startup" #define JSD_AUTOREG_ENTRY "JSDebugger Startup Observer" #define JSD_STARTUP_ENTRY "JSDebugger Startup Observer" static void jsds_GCSliceCallbackProc (JSRuntime *rt, JS::GCProgress progress, const JS::GCDescription &desc); /******************************************************************************* * global vars ******************************************************************************/ const char implementationString[] = "Mozilla JavaScript Debugger Service"; const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1"; const char jsdARObserverCtrID[] = "@mozilla.org/js/jsd/app-start-observer;2"; const char jsdASObserverCtrID[] = "service,@mozilla.org/js/jsd/app-start-observer;2"; #ifdef DEBUG_verbose uint32_t gScriptCount = 0; uint32_t gValueCount = 0; uint32_t gPropertyCount = 0; uint32_t gContextCount = 0; uint32_t gFrameCount = 0; #endif static jsdService *gJsds = 0; static JS::GCSliceCallback gPrevGCSliceCallback = jsds_GCSliceCallbackProc; static bool gGCRunning = false; static struct DeadScript { PRCList links; JSDContext *jsdc; jsdIScript *script; } *gDeadScripts = nullptr; enum PatternType { ptIgnore = 0U, ptStartsWith = 1U, ptEndsWith = 2U, ptContains = 3U, ptEquals = 4U }; static struct FilterRecord { PRCList links; jsdIFilter *filterObject; nsCString urlPattern; PatternType patternType; uint32_t startLine; uint32_t endLine; } *gFilters = nullptr; static struct LiveEphemeral *gLiveValues = nullptr; static struct LiveEphemeral *gLiveProperties = nullptr; static struct LiveEphemeral *gLiveContexts = nullptr; static struct LiveEphemeral *gLiveStackFrames = nullptr; /******************************************************************************* * utility functions for ephemeral lists *******************************************************************************/ already_AddRefed jsds_FindEphemeral (LiveEphemeral **listHead, void *key) { if (!*listHead) return nullptr; LiveEphemeral *lv_record = reinterpret_cast (PR_NEXT_LINK(&(*listHead)->links)); do { if (lv_record->key == key) { nsCOMPtr ret = lv_record->value; return ret.forget(); } lv_record = reinterpret_cast (PR_NEXT_LINK(&lv_record->links)); } while (lv_record != *listHead); return nullptr; } void jsds_InvalidateAllEphemerals (LiveEphemeral **listHead) { LiveEphemeral *lv_record = reinterpret_cast (PR_NEXT_LINK(&(*listHead)->links)); do { LiveEphemeral *next = reinterpret_cast (PR_NEXT_LINK(&lv_record->links)); lv_record->value->Invalidate(); lv_record = next; } while (*listHead); } void jsds_InsertEphemeral (LiveEphemeral **listHead, LiveEphemeral *item) { if (*listHead) { /* if the list exists, add to it */ PR_APPEND_LINK(&item->links, &(*listHead)->links); } else { /* otherwise create the list */ PR_INIT_CLIST(&item->links); *listHead = item; } } void jsds_RemoveEphemeral (LiveEphemeral **listHead, LiveEphemeral *item) { LiveEphemeral *next = reinterpret_cast (PR_NEXT_LINK(&item->links)); if (next == item) { /* if the current item is also the next item, we're the only element, * null out the list head */ NS_ASSERTION (*listHead == item, "How could we not be the head of a one item list?"); *listHead = nullptr; } else if (item == *listHead) { /* otherwise, if we're currently the list head, change it */ *listHead = next; } PR_REMOVE_AND_INIT_LINK(&item->links); } /******************************************************************************* * utility functions for filters *******************************************************************************/ void jsds_FreeFilter (FilterRecord *rec) { NS_IF_RELEASE (rec->filterObject); PR_Free (rec); } /* copies appropriate |filter| attributes into |rec|. * False return indicates failure, the contents of |rec| will not be changed. */ bool jsds_SyncFilter (FilterRecord *rec, jsdIFilter *filter) { NS_ASSERTION (rec, "jsds_SyncFilter without rec"); NS_ASSERTION (filter, "jsds_SyncFilter without filter"); uint32_t startLine; nsresult rv = filter->GetStartLine(&startLine); if (NS_FAILED(rv)) return false; uint32_t endLine; rv = filter->GetStartLine(&endLine); if (NS_FAILED(rv)) return false; nsAutoCString urlPattern; rv = filter->GetUrlPattern (urlPattern); if (NS_FAILED(rv)) return false; uint32_t len = urlPattern.Length(); if (len) { if (urlPattern[0] == '*') { /* pattern starts with a *, shift all chars once to the left, * including the trailing null. */ urlPattern = Substring(urlPattern, 1, len); if (urlPattern[len - 2] == '*') { /* pattern is in the format "*foo*", overwrite the final * with * a null. */ urlPattern.Truncate(len - 2); rec->patternType = ptContains; } else { /* pattern is in the format "*foo", just make a note of the * new length. */ rec->patternType = ptEndsWith; } } else if (urlPattern[len - 1] == '*') { /* pattern is in the format "foo*", overwrite the final * with a * null. */ urlPattern.Truncate(len - 1); rec->patternType = ptStartsWith; } else { /* pattern is in the format "foo". */ rec->patternType = ptEquals; } } else { rec->patternType = ptIgnore; } /* we got everything we need without failing, now copy it into rec. */ if (rec->filterObject != filter) { NS_IF_RELEASE(rec->filterObject); NS_ADDREF(filter); rec->filterObject = filter; } rec->startLine = startLine; rec->endLine = endLine; rec->urlPattern = urlPattern; return true; } FilterRecord * jsds_FindFilter (jsdIFilter *filter) { if (!gFilters) return nullptr; FilterRecord *current = gFilters; do { if (current->filterObject == filter) return current; current = reinterpret_cast (PR_NEXT_LINK(¤t->links)); } while (current != gFilters); return nullptr; } /* returns true if the hook should be executed. */ bool jsds_FilterHook (JSDContext *jsdc, JSDThreadState *state) { JSDStackFrameInfo *frame = JSD_GetStackFrame (jsdc, state); if (!frame) { NS_WARNING("No frame in threadstate"); return false; } JSDScript *script = JSD_GetScriptForStackFrame (jsdc, state, frame); if (!script) return true; uintptr_t pc = JSD_GetPCForStackFrame (jsdc, state, frame); nsCString url(JSD_GetScriptFilename (jsdc, script)); if (url.IsEmpty()) { NS_WARNING ("Script with no filename"); return false; } if (!gFilters) return true; uint32_t currentLine = JSD_GetClosestLine (jsdc, script, pc); uint32_t len = 0; FilterRecord *currentFilter = gFilters; do { uint32_t flags = 0; #ifdef DEBUG nsresult rv = #endif currentFilter->filterObject->GetFlags(&flags); NS_ASSERTION(NS_SUCCEEDED(rv), "Error getting flags for filter"); if (flags & jsdIFilter::FLAG_ENABLED) { /* If there is no start line, or the start line is before * or equal to the current */ if ((!currentFilter->startLine || currentFilter->startLine <= currentLine) && /* and there is no end line, or the end line is after * or equal to the current */ (!currentFilter->endLine || currentFilter->endLine >= currentLine)) { /* then we're going to have to compare the url. */ if (currentFilter->patternType == ptIgnore) return !!(flags & jsdIFilter::FLAG_PASS); if (!len) len = url.Length(); nsCString urlPattern = currentFilter->urlPattern; uint32_t patternLength = urlPattern.Length(); if (len >= patternLength) { switch (currentFilter->patternType) { case ptEquals: if (urlPattern.Equals(url)) return !!(flags & jsdIFilter::FLAG_PASS); break; case ptStartsWith: if (urlPattern.Equals(Substring(url, 0, patternLength))) return !!(flags & jsdIFilter::FLAG_PASS); break; case ptEndsWith: if (urlPattern.Equals(Substring(url, len - patternLength))) return !!(flags & jsdIFilter::FLAG_PASS); break; case ptContains: { nsACString::const_iterator start, end; url.BeginReading(start); url.EndReading(end); if (FindInReadable(currentFilter->urlPattern, start, end)) return !!(flags & jsdIFilter::FLAG_PASS); } break; default: NS_ERROR("Invalid pattern type"); } } } } currentFilter = reinterpret_cast (PR_NEXT_LINK(¤tFilter->links)); } while (currentFilter != gFilters); return true; } /******************************************************************************* * c callbacks *******************************************************************************/ static void jsds_NotifyPendingDeadScripts (JSRuntime *rt) { jsdService *jsds = gJsds; nsCOMPtr hook; if (jsds) { NS_ADDREF(jsds); jsds->GetScriptHook (getter_AddRefs(hook)); jsds->DoPause(nullptr, true); } DeadScript *deadScripts = gDeadScripts; gDeadScripts = nullptr; while (deadScripts) { DeadScript *ds = deadScripts; /* get next deleted script */ deadScripts = reinterpret_cast (PR_NEXT_LINK(&ds->links)); if (deadScripts == ds) deadScripts = nullptr; if (hook) { /* tell the user this script has been destroyed */ #ifdef CAUTIOUS_SCRIPTHOOK JS_UNKEEP_ATOMS(rt); #endif hook->OnScriptDestroyed (ds->script); #ifdef CAUTIOUS_SCRIPTHOOK JS_KEEP_ATOMS(rt); #endif } /* take it out of the circular list */ PR_REMOVE_LINK(&ds->links); /* addref came from the FromPtr call in jsds_ScriptHookProc */ NS_RELEASE(ds->script); /* free the struct! */ PR_Free(ds); } if (jsds) { jsds->DoUnPause(nullptr, true); NS_RELEASE(jsds); } } static void jsds_GCSliceCallbackProc (JSRuntime *rt, JS::GCProgress progress, const JS::GCDescription &desc) { if (progress == JS::GC_CYCLE_END || progress == JS::GC_SLICE_END) { NS_ASSERTION(gGCRunning, "GC slice callback was missed"); while (gDeadScripts) jsds_NotifyPendingDeadScripts (rt); gGCRunning = false; } else { NS_ASSERTION(!gGCRunning, "should not re-enter GC"); gGCRunning = true; } if (gPrevGCSliceCallback) (*gPrevGCSliceCallback)(rt, progress, desc); } static unsigned jsds_ErrorHookProc (JSDContext *jsdc, JSContext *cx, const char *message, JSErrorReport *report, void *callerdata) { static bool running = false; nsCOMPtr hook; gJsds->GetErrorHook(getter_AddRefs(hook)); if (!hook) return JSD_ERROR_REPORTER_PASS_ALONG; if (running) return JSD_ERROR_REPORTER_PASS_ALONG; running = true; nsCOMPtr val; if (JS_IsExceptionPending(cx)) { jsval jv; JS_GetPendingException(cx, &jv); JSDValue *jsdv = JSD_NewValue (jsdc, jv); val = getter_AddRefs(jsdValue::FromPtr(jsdc, jsdv)); } nsAutoCString fileName; uint32_t line; uint32_t pos; uint32_t flags; uint32_t errnum; bool rval; if (report) { fileName.Assign(report->filename); line = report->lineno; pos = report->tokenptr - report->linebuf; flags = report->flags; errnum = report->errorNumber; } else { line = 0; pos = 0; flags = 0; errnum = 0; } gJsds->DoPause(nullptr, true); hook->OnError (nsDependentCString(message), fileName, line, pos, flags, errnum, val, &rval); gJsds->DoUnPause(nullptr, true); running = false; if (!rval) return JSD_ERROR_REPORTER_DEBUG; return JSD_ERROR_REPORTER_PASS_ALONG; } static JSBool jsds_CallHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate, unsigned type, void* callerdata) { nsCOMPtr hook; switch (type) { case JSD_HOOK_TOPLEVEL_START: case JSD_HOOK_TOPLEVEL_END: gJsds->GetTopLevelHook(getter_AddRefs(hook)); break; case JSD_HOOK_FUNCTION_CALL: case JSD_HOOK_FUNCTION_RETURN: gJsds->GetFunctionHook(getter_AddRefs(hook)); break; default: NS_ASSERTION (0, "Unknown hook type."); } if (!hook) return JS_TRUE; if (!jsds_FilterHook (jsdc, jsdthreadstate)) return JS_FALSE; JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate); nsCOMPtr frame = getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, native_frame)); gJsds->DoPause(nullptr, true); hook->OnCall(frame, type); gJsds->DoUnPause(nullptr, true); jsdStackFrame::InvalidateAll(); return JS_TRUE; } static uint32_t jsds_ExecutionHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate, unsigned type, void* callerdata, jsval* rval) { nsCOMPtr hook(0); uint32_t hook_rv = JSD_HOOK_RETURN_CONTINUE; nsCOMPtr js_rv; switch (type) { case JSD_HOOK_INTERRUPTED: gJsds->GetInterruptHook(getter_AddRefs(hook)); break; case JSD_HOOK_DEBUG_REQUESTED: gJsds->GetDebugHook(getter_AddRefs(hook)); break; case JSD_HOOK_DEBUGGER_KEYWORD: gJsds->GetDebuggerHook(getter_AddRefs(hook)); break; case JSD_HOOK_BREAKPOINT: { /* we can't pause breakpoints the way we pause the other * execution hooks (at least, not easily.) Instead we bail * here if the service is paused. */ uint32_t level; gJsds->GetPauseDepth(&level); if (!level) gJsds->GetBreakpointHook(getter_AddRefs(hook)); } break; case JSD_HOOK_THROW: { hook_rv = JSD_HOOK_RETURN_CONTINUE_THROW; gJsds->GetThrowHook(getter_AddRefs(hook)); if (hook) { JSDValue *jsdv = JSD_GetException (jsdc, jsdthreadstate); js_rv = getter_AddRefs(jsdValue::FromPtr (jsdc, jsdv)); } break; } default: NS_ASSERTION (0, "Unknown hook type."); } if (!hook) return hook_rv; if (!jsds_FilterHook (jsdc, jsdthreadstate)) return JSD_HOOK_RETURN_CONTINUE; JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate); nsCOMPtr frame = getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, native_frame)); gJsds->DoPause(nullptr, true); jsdIValue *inout_rv = js_rv; NS_IF_ADDREF(inout_rv); hook->OnExecute (frame, type, &inout_rv, &hook_rv); js_rv = inout_rv; NS_IF_RELEASE(inout_rv); gJsds->DoUnPause(nullptr, true); jsdStackFrame::InvalidateAll(); if (hook_rv == JSD_HOOK_RETURN_RET_WITH_VAL || hook_rv == JSD_HOOK_RETURN_THROW_WITH_VAL) { *rval = JSVAL_VOID; if (js_rv) { JSDValue *jsdv; if (NS_SUCCEEDED(js_rv->GetJSDValue (&jsdv))) *rval = JSD_GetValueWrappedJSVal(jsdc, jsdv); } } return hook_rv; } static void jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, void* callerdata) { #ifdef CAUTIOUS_SCRIPTHOOK JSContext *cx = JSD_GetDefaultJSContext(jsdc); JSRuntime *rt = JS_GetRuntime(cx); #endif if (creating) { nsCOMPtr hook; gJsds->GetScriptHook(getter_AddRefs(hook)); /* a script is being created */ if (!hook) { /* nobody cares, just exit */ return; } nsCOMPtr script = getter_AddRefs(jsdScript::FromPtr(jsdc, jsdscript)); #ifdef CAUTIOUS_SCRIPTHOOK JS_UNKEEP_ATOMS(rt); #endif gJsds->DoPause(nullptr, true); hook->OnScriptCreated (script); gJsds->DoUnPause(nullptr, true); #ifdef CAUTIOUS_SCRIPTHOOK JS_KEEP_ATOMS(rt); #endif } else { /* a script is being destroyed. even if there is no registered hook * we'll still need to invalidate the jsdIScript record, in order * to remove the reference held in the JSDScript private data. */ nsCOMPtr jsdis = static_cast(JSD_GetScriptPrivate(jsdscript)); if (!jsdis) return; jsdis->Invalidate(); if (!gGCRunning) { nsCOMPtr hook; gJsds->GetScriptHook(getter_AddRefs(hook)); if (!hook) return; /* if GC *isn't* running, we can tell the user about the script * delete now. */ #ifdef CAUTIOUS_SCRIPTHOOK JS_UNKEEP_ATOMS(rt); #endif gJsds->DoPause(nullptr, true); hook->OnScriptDestroyed (jsdis); gJsds->DoUnPause(nullptr, true); #ifdef CAUTIOUS_SCRIPTHOOK JS_KEEP_ATOMS(rt); #endif } else { /* if a GC *is* running, we've got to wait until it's done before * we can execute any JS, so we queue the notification in a PRCList * until GC tells us it's done. See jsds_GCCallbackProc(). */ DeadScript *ds = PR_NEW(DeadScript); if (!ds) return; /* NS_ERROR_OUT_OF_MEMORY */ ds->jsdc = jsdc; ds->script = jsdis; NS_ADDREF(ds->script); if (gDeadScripts) /* if the queue exists, add to it */ PR_APPEND_LINK(&ds->links, &gDeadScripts->links); else { /* otherwise create the queue */ PR_INIT_CLIST(&ds->links); gDeadScripts = ds; } } } } /******************************************************************************* * reflected jsd data structures *******************************************************************************/ /* Contexts */ /* NS_IMPL_THREADSAFE_ISUPPORTS2(jsdContext, jsdIContext, jsdIEphemeral); NS_IMETHODIMP jsdContext::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } */ /* Objects */ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdObject, jsdIObject) NS_IMETHODIMP jsdObject::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdObject::GetJSDObject(JSDObject **_rval) { *_rval = mObject; return NS_OK; } NS_IMETHODIMP jsdObject::GetCreatorURL(nsACString &_rval) { _rval.Assign(JSD_GetObjectNewURL(mCx, mObject)); return NS_OK; } NS_IMETHODIMP jsdObject::GetCreatorLine(uint32_t *_rval) { *_rval = JSD_GetObjectNewLineNumber(mCx, mObject); return NS_OK; } NS_IMETHODIMP jsdObject::GetConstructorURL(nsACString &_rval) { _rval.Assign(JSD_GetObjectConstructorURL(mCx, mObject)); return NS_OK; } NS_IMETHODIMP jsdObject::GetConstructorLine(uint32_t *_rval) { *_rval = JSD_GetObjectConstructorLineNumber(mCx, mObject); return NS_OK; } NS_IMETHODIMP jsdObject::GetValue(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetValueForObject (mCx, mObject); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } /* Properties */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdProperty, jsdIProperty, jsdIEphemeral) jsdProperty::jsdProperty (JSDContext *aCx, JSDProperty *aProperty) : mCx(aCx), mProperty(aProperty) { DEBUG_CREATE ("jsdProperty", gPropertyCount); mValid = (aCx && aProperty); mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveProperties, &mLiveListEntry); } jsdProperty::~jsdProperty () { DEBUG_DESTROY ("jsdProperty", gPropertyCount); if (mValid) Invalidate(); } NS_IMETHODIMP jsdProperty::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = false; jsds_RemoveEphemeral (&gLiveProperties, &mLiveListEntry); JSD_DropProperty (mCx, mProperty); return NS_OK; } void jsdProperty::InvalidateAll() { if (gLiveProperties) jsds_InvalidateAllEphemerals (&gLiveProperties); } NS_IMETHODIMP jsdProperty::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdProperty::GetJSDProperty(JSDProperty **_rval) { *_rval = mProperty; return NS_OK; } NS_IMETHODIMP jsdProperty::GetIsValid(bool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdProperty::GetAlias(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetPropertyValue (mCx, mProperty); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdProperty::GetFlags(uint32_t *_rval) { *_rval = JSD_GetPropertyFlags (mCx, mProperty); return NS_OK; } NS_IMETHODIMP jsdProperty::GetName(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetPropertyName (mCx, mProperty); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdProperty::GetValue(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetPropertyValue (mCx, mProperty); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } /* Scripts */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdScript, jsdIScript, jsdIEphemeral) static NS_IMETHODIMP AssignToJSString(JSDContext *aCx, nsACString *x, JSString *str) { if (!str) { x->SetLength(0); return NS_OK; } JSContext *cx = JSD_GetDefaultJSContext(aCx); size_t length = JS_GetStringEncodingLength(cx, str); if (length == size_t(-1)) return NS_ERROR_FAILURE; x->SetLength(uint32_t(length)); if (x->Length() != uint32_t(length)) return NS_ERROR_OUT_OF_MEMORY; JS_EncodeStringToBuffer(cx, str, x->BeginWriting(), length); return NS_OK; } jsdScript::jsdScript (JSDContext *aCx, JSDScript *aScript) : mValid(false), mTag(0), mCx(aCx), mScript(aScript), mFileName(0), mFunctionName(0), mBaseLineNumber(0), mLineExtent(0), mPPLineMap(0), mFirstPC(0) { DEBUG_CREATE ("jsdScript", gScriptCount); if (mScript) { /* copy the script's information now, so we have it later, when it * gets destroyed. */ JSD_LockScriptSubsystem(mCx); mFileName = new nsCString(JSD_GetScriptFilename(mCx, mScript)); mFunctionName = new nsCString(); if (mFunctionName) { JSString *str = JSD_GetScriptFunctionId(mCx, mScript); if (str) AssignToJSString(mCx, mFunctionName, str); } mBaseLineNumber = JSD_GetScriptBaseLineNumber(mCx, mScript); mLineExtent = JSD_GetScriptLineExtent(mCx, mScript); mFirstPC = JSD_GetClosestPC(mCx, mScript, 0); JSD_UnlockScriptSubsystem(mCx); mValid = true; } } jsdScript::~jsdScript () { DEBUG_DESTROY ("jsdScript", gScriptCount); delete mFileName; delete mFunctionName; if (mPPLineMap) PR_Free(mPPLineMap); /* Invalidate() needs to be called to release an owning reference to * ourselves, so if we got here without being invalidated, something * has gone wrong with our ref count. */ NS_ASSERTION (!mValid, "Script destroyed without being invalidated."); } /* * This method populates a line <-> pc map for a pretty printed version of this * script. It does this by decompiling, and then recompiling the script. The * resulting script is scanned for the line map, and then left as GC fodder. */ PCMapEntry * jsdScript::CreatePPLineMap() { JSContext *cx = JSD_GetDefaultJSContext (mCx); JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL)); if (!obj) return nullptr; JS::RootedFunction fun(cx, JSD_GetJSFunction (mCx, mScript)); JS::RootedScript script(cx); /* In JSD compartment */ uint32_t baseLine; JS::RootedString jsstr(cx); size_t length; const jschar *chars; if (fun) { unsigned nargs; { JSAutoCompartment ac(cx, JS_GetFunctionObject(fun)); nargs = JS_GetFunctionArgumentCount(cx, fun); if (nargs > 12) return nullptr; jsstr = JS_DecompileFunctionBody (cx, fun, 4); if (!jsstr) return nullptr; if (!(chars = JS_GetStringCharsAndLength(cx, jsstr, &length))) return nullptr; } JS::Anchor kungFuDeathGrip(jsstr); const char *argnames[] = {"arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8", "arg9", "arg10", "arg11", "arg12" }; fun = JS_CompileUCFunction (cx, obj, "ppfun", nargs, argnames, chars, length, "x-jsd:ppbuffer?type=function", 3); if (!fun || !(script = JS_GetFunctionScript(cx, fun))) return nullptr; baseLine = 3; } else { script = JSD_GetJSScript(mCx, mScript); JSString *jsstr; { JSAutoCompartment ac(cx, script); jsstr = JS_DecompileScript (cx, script, "ppscript", 4); if (!jsstr) return nullptr; if (!(chars = JS_GetStringCharsAndLength(cx, jsstr, &length))) return nullptr; } JS::Anchor kungFuDeathGrip(jsstr); script = JS_CompileUCScript (cx, obj, chars, length, "x-jsd:ppbuffer?type=script", 1); if (!script) return nullptr; baseLine = 1; } uint32_t scriptExtent = JS_GetScriptLineExtent (cx, script); jsbytecode* firstPC = JS_LineNumberToPC (cx, script, 0); /* allocate worst case size of map (number of lines in script + 1 * for our 0 record), we'll shrink it with a realloc later. */ PCMapEntry *lineMap = static_cast (PR_Malloc((scriptExtent + 1) * sizeof (PCMapEntry))); uint32_t lineMapSize = 0; if (lineMap) { for (uint32_t line = baseLine; line < scriptExtent + baseLine; ++line) { jsbytecode* pc = JS_LineNumberToPC (cx, script, line); if (line == JS_PCToLineNumber (cx, script, pc)) { lineMap[lineMapSize].line = line; lineMap[lineMapSize].pc = pc - firstPC; ++lineMapSize; } } if (scriptExtent != lineMapSize) { lineMap = static_cast (PR_Realloc(mPPLineMap = lineMap, lineMapSize * sizeof(PCMapEntry))); if (!lineMap) { PR_Free(mPPLineMap); lineMapSize = 0; } } } mPCMapSize = lineMapSize; return mPPLineMap = lineMap; } uint32_t jsdScript::PPPcToLine (uint32_t aPC) { if (!mPPLineMap && !CreatePPLineMap()) return 0; uint32_t i; for (i = 1; i < mPCMapSize; ++i) { if (mPPLineMap[i].pc > aPC) return mPPLineMap[i - 1].line; } return mPPLineMap[mPCMapSize - 1].line; } uint32_t jsdScript::PPLineToPc (uint32_t aLine) { if (!mPPLineMap && !CreatePPLineMap()) return 0; uint32_t i; for (i = 1; i < mPCMapSize; ++i) { if (mPPLineMap[i].line > aLine) return mPPLineMap[i - 1].pc; } return mPPLineMap[mPCMapSize - 1].pc; } NS_IMETHODIMP jsdScript::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdScript::GetJSDScript(JSDScript **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mScript; return NS_OK; } NS_IMETHODIMP jsdScript::GetVersion (int32_t *_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); JS::RootedScript script(cx, JSD_GetJSScript(mCx, mScript)); JSAutoCompartment ac(cx, script); *_rval = static_cast(JS_GetScriptVersion(cx, script)); return NS_OK; } NS_IMETHODIMP jsdScript::GetTag(uint32_t *_rval) { if (!mTag) mTag = ++jsdScript::LastTag; *_rval = mTag; return NS_OK; } NS_IMETHODIMP jsdScript::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = false; /* release the addref we do in FromPtr */ jsdIScript *script = static_cast (JSD_GetScriptPrivate(mScript)); NS_ASSERTION (script == this, "That's not my script!"); NS_RELEASE(script); JSD_SetScriptPrivate(mScript, NULL); return NS_OK; } void jsdScript::InvalidateAll () { JSDContext *cx; if (NS_FAILED(gJsds->GetJSDContext (&cx))) return; JSDScript *script; JSDScript *iter = NULL; JSD_LockScriptSubsystem(cx); while((script = JSD_IterateScripts(cx, &iter)) != NULL) { nsCOMPtr jsdis = static_cast(JSD_GetScriptPrivate(script)); if (jsdis) jsdis->Invalidate(); } JSD_UnlockScriptSubsystem(cx); } NS_IMETHODIMP jsdScript::GetIsValid(bool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdScript::SetFlags(uint32_t flags) { ASSERT_VALID_EPHEMERAL; JSD_SetScriptFlags(mCx, mScript, flags); return NS_OK; } NS_IMETHODIMP jsdScript::GetFlags(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptFlags(mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetFileName(nsACString &_rval) { _rval.Assign(*mFileName); return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionName(nsACString &_rval) { _rval.Assign(*mFunctionName); return NS_OK; } NS_IMETHODIMP jsdScript::GetParameterNames(uint32_t* count, PRUnichar*** paramNames) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!cx) { NS_WARNING("No default context !?"); return NS_ERROR_FAILURE; } JS::RootedFunction fun(cx, JSD_GetJSFunction (mCx, mScript)); if (!fun) { *count = 0; *paramNames = nullptr; return NS_OK; } JSAutoCompartment ac(cx, JS_GetFunctionObject(fun)); unsigned nargs; if (!JS_FunctionHasLocalNames(cx, fun) || (nargs = JS_GetFunctionArgumentCount(cx, fun)) == 0) { *count = 0; *paramNames = nullptr; return NS_OK; } PRUnichar **ret = static_cast(NS_Alloc(nargs * sizeof(PRUnichar*))); if (!ret) return NS_ERROR_OUT_OF_MEMORY; void *mark; uintptr_t *names = JS_GetFunctionLocalNameArray(cx, fun, &mark); if (!names) { NS_Free(ret); return NS_ERROR_OUT_OF_MEMORY; } nsresult rv = NS_OK; for (unsigned i = 0; i < nargs; ++i) { JSAtom *atom = JS_LocalNameToAtom(names[i]); if (!atom) { ret[i] = 0; } else { JSString *str = JS_AtomKey(atom); ret[i] = NS_strndup(JS_GetInternedStringChars(str), JS_GetStringLength(str)); if (!ret[i]) { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret); rv = NS_ERROR_OUT_OF_MEMORY; break; } } } JS_ReleaseFunctionLocalNameArray(cx, mark); if (NS_FAILED(rv)) return rv; *count = nargs; *paramNames = ret; return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionObject(jsdIValue **_rval) { JSFunction *fun = JSD_GetJSFunction(mCx, mScript); if (!fun) return NS_ERROR_NOT_AVAILABLE; JSContext *jsContext = JSD_GetDefaultJSContext (mCx); if (!jsContext) { return NS_ERROR_FAILURE; } JS::RootedObject obj(jsContext, JS_GetFunctionObject(fun)); if (!obj) return NS_ERROR_FAILURE; JSDContext *cx; if (NS_FAILED(gJsds->GetJSDContext (&cx))) return NS_ERROR_NOT_INITIALIZED; JSDValue *jsdv = JSD_NewValue(cx, OBJECT_TO_JSVAL(obj)); if (!jsdv) return NS_ERROR_OUT_OF_MEMORY; *_rval = jsdValue::FromPtr(cx, jsdv); if (!*_rval) { JSD_DropValue(cx, jsdv); return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionSource(nsAString & aFunctionSource) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!cx) { NS_WARNING("No default context !?"); return NS_ERROR_FAILURE; } JS::RootedFunction fun(cx, JSD_GetJSFunction (mCx, mScript)); JSString *jsstr; mozilla::Maybe ac; if (fun) { ac.construct(cx, JS_GetFunctionObject(fun)); jsstr = JS_DecompileFunction (cx, fun, 4); } else { JS::RootedScript script(cx, JSD_GetJSScript (mCx, mScript)); ac.construct(cx, script); jsstr = JS_DecompileScript (cx, script, "ppscript", 4); } if (!jsstr) return NS_ERROR_FAILURE; size_t length; const jschar *chars = JS_GetStringCharsZAndLength(cx, jsstr, &length); if (!chars) return NS_ERROR_FAILURE; aFunctionSource = nsDependentString(chars, length); return NS_OK; } NS_IMETHODIMP jsdScript::GetBaseLineNumber(uint32_t *_rval) { *_rval = mBaseLineNumber; return NS_OK; } NS_IMETHODIMP jsdScript::GetLineExtent(uint32_t *_rval) { *_rval = mLineExtent; return NS_OK; } NS_IMETHODIMP jsdScript::GetCallCount(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptCallCount (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMaxRecurseDepth(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMaxRecurseDepth (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMinExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMinExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMaxExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMaxExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetTotalExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptTotalExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMinOwnExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMinOwnExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMaxOwnExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMaxOwnExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetTotalOwnExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptTotalOwnExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::ClearProfileData() { ASSERT_VALID_EPHEMERAL; JSD_ClearScriptProfileData(mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::PcToLine(uint32_t aPC, uint32_t aPcmap, uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { *_rval = JSD_GetClosestLine (mCx, mScript, mFirstPC + aPC); } else if (aPcmap == PCMAP_PRETTYPRINT) { *_rval = PPPcToLine(aPC); } else { return NS_ERROR_INVALID_ARG; } return NS_OK; } NS_IMETHODIMP jsdScript::LineToPc(uint32_t aLine, uint32_t aPcmap, uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { uintptr_t pc = JSD_GetClosestPC (mCx, mScript, aLine); *_rval = pc - mFirstPC; } else if (aPcmap == PCMAP_PRETTYPRINT) { *_rval = PPLineToPc(aLine); } else { return NS_ERROR_INVALID_ARG; } return NS_OK; } NS_IMETHODIMP jsdScript::EnableSingleStepInterrupts(bool enable) { ASSERT_VALID_EPHEMERAL; /* Must have set interrupt hook before enabling */ if (enable && !jsdService::GetService()->CheckInterruptHook()) return NS_ERROR_NOT_INITIALIZED; return (JSD_EnableSingleStepInterrupts(mCx, mScript, enable) ? NS_OK : NS_ERROR_FAILURE); } NS_IMETHODIMP jsdScript::GetExecutableLines(uint32_t aPcmap, uint32_t aStartLine, uint32_t aMaxLines, uint32_t* aCount, uint32_t** aExecutableLines) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { uintptr_t start = JSD_GetClosestPC(mCx, mScript, 0); unsigned lastLine = JSD_GetScriptBaseLineNumber(mCx, mScript) + JSD_GetScriptLineExtent(mCx, mScript) - 1; uintptr_t end = JSD_GetClosestPC(mCx, mScript, lastLine + 1); *aExecutableLines = static_cast(NS_Alloc((end - start + 1) * sizeof(uint32_t))); if (!JSD_GetLinePCs(mCx, mScript, aStartLine, aMaxLines, aCount, aExecutableLines, NULL)) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } if (aPcmap == PCMAP_PRETTYPRINT) { if (!mPPLineMap) { if (!CreatePPLineMap()) return NS_ERROR_OUT_OF_MEMORY; } nsTArray lines; uint32_t i; for (i = 0; i < mPCMapSize; ++i) { if (mPPLineMap[i].line >= aStartLine) break; } for (; i < mPCMapSize && lines.Length() < aMaxLines; ++i) { lines.AppendElement(mPPLineMap[i].line); } if (aCount) *aCount = lines.Length(); *aExecutableLines = static_cast(NS_Alloc(lines.Length() * sizeof(uint32_t))); if (!*aExecutableLines) return NS_ERROR_OUT_OF_MEMORY; for (i = 0; i < lines.Length(); ++i) (*aExecutableLines)[i] = lines[i]; return NS_OK; } return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP jsdScript::IsLineExecutable(uint32_t aLine, uint32_t aPcmap, bool *_rval) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { uintptr_t pc = JSD_GetClosestPC (mCx, mScript, aLine); *_rval = (aLine == JSD_GetClosestLine (mCx, mScript, pc)); } else if (aPcmap == PCMAP_PRETTYPRINT) { if (!mPPLineMap && !CreatePPLineMap()) return NS_ERROR_OUT_OF_MEMORY; *_rval = false; for (uint32_t i = 0; i < mPCMapSize; ++i) { if (mPPLineMap[i].line >= aLine) { *_rval = (mPPLineMap[i].line == aLine); break; } } } else { return NS_ERROR_INVALID_ARG; } return NS_OK; } NS_IMETHODIMP jsdScript::SetBreakpoint(uint32_t aPC) { ASSERT_VALID_EPHEMERAL; uintptr_t pc = mFirstPC + aPC; JSD_SetExecutionHook (mCx, mScript, pc, jsds_ExecutionHookProc, NULL); return NS_OK; } NS_IMETHODIMP jsdScript::ClearBreakpoint(uint32_t aPC) { ASSERT_VALID_EPHEMERAL; uintptr_t pc = mFirstPC + aPC; JSD_ClearExecutionHook (mCx, mScript, pc); return NS_OK; } NS_IMETHODIMP jsdScript::ClearAllBreakpoints() { ASSERT_VALID_EPHEMERAL; JSD_LockScriptSubsystem(mCx); JSD_ClearAllExecutionHooksForScript (mCx, mScript); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } /* Contexts */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdContext, jsdIContext, jsdIEphemeral) jsdIContext * jsdContext::FromPtr (JSDContext *aJSDCx, JSContext *aJSCx) { if (!aJSDCx || !aJSCx) return nullptr; nsCOMPtr jsdicx; nsCOMPtr eph = jsds_FindEphemeral (&gLiveContexts, static_cast(aJSCx)); if (eph) { jsdicx = do_QueryInterface(eph); } else { nsCOMPtr iscx; if (JS_GetOptions(aJSCx) & JSOPTION_PRIVATE_IS_NSISUPPORTS) iscx = static_cast(JS_GetContextPrivate(aJSCx)); jsdicx = new jsdContext (aJSDCx, aJSCx, iscx); } jsdIContext *ctx = nullptr; jsdicx.swap(ctx); return ctx; } jsdContext::jsdContext (JSDContext *aJSDCx, JSContext *aJSCx, nsISupports *aISCx) : mValid(true), mTag(0), mJSDCx(aJSDCx), mJSCx(aJSCx), mISCx(aISCx) { DEBUG_CREATE ("jsdContext", gContextCount); mLiveListEntry.value = this; mLiveListEntry.key = static_cast(aJSCx); jsds_InsertEphemeral (&gLiveContexts, &mLiveListEntry); } jsdContext::~jsdContext() { DEBUG_DESTROY ("jsdContext", gContextCount); if (mValid) { /* call Invalidate() to take ourselves out of the live list */ Invalidate(); } } NS_IMETHODIMP jsdContext::GetIsValid(bool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdContext::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = false; jsds_RemoveEphemeral (&gLiveContexts, &mLiveListEntry); return NS_OK; } void jsdContext::InvalidateAll() { if (gLiveContexts) jsds_InvalidateAllEphemerals (&gLiveContexts); } NS_IMETHODIMP jsdContext::GetJSContext(JSContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mJSCx; return NS_OK; } NS_IMETHODIMP jsdContext::GetOptions(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JS_GetOptions(mJSCx); return NS_OK; } NS_IMETHODIMP jsdContext::SetOptions(uint32_t options) { ASSERT_VALID_EPHEMERAL; uint32_t lastOptions = JS_GetOptions(mJSCx); /* don't let users change this option, they'd just be shooting themselves * in the foot. */ if ((options ^ lastOptions) & JSOPTION_PRIVATE_IS_NSISUPPORTS) return NS_ERROR_ILLEGAL_VALUE; JS_SetOptions(mJSCx, options); return NS_OK; } NS_IMETHODIMP jsdContext::GetPrivateData(nsISupports **_rval) { ASSERT_VALID_EPHEMERAL; uint32_t options = JS_GetOptions(mJSCx); if (options & JSOPTION_PRIVATE_IS_NSISUPPORTS) { *_rval = static_cast(JS_GetContextPrivate(mJSCx)); NS_IF_ADDREF(*_rval); } else { *_rval = nullptr; } return NS_OK; } NS_IMETHODIMP jsdContext::GetWrappedContext(nsISupports **_rval) { ASSERT_VALID_EPHEMERAL; NS_IF_ADDREF(*_rval = mISCx); return NS_OK; } NS_IMETHODIMP jsdContext::GetTag(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; if (!mTag) mTag = ++jsdContext::LastTag; *_rval = mTag; return NS_OK; } NS_IMETHODIMP jsdContext::GetVersion (int32_t *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = static_cast(JS_GetVersion(mJSCx)); return NS_OK; } NS_IMETHODIMP jsdContext::SetVersion (int32_t id) { ASSERT_VALID_EPHEMERAL; JSVersion ver = static_cast(id); JS_SetVersion(mJSCx, ver); return NS_OK; } NS_IMETHODIMP jsdContext::GetGlobalObject (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSObject *glob = js::GetDefaultGlobalForContext(mJSCx); JSDValue *jsdv = JSD_NewValue (mJSDCx, OBJECT_TO_JSVAL(glob)); if (!jsdv) return NS_ERROR_FAILURE; *_rval = jsdValue::FromPtr (mJSDCx, jsdv); if (!*_rval) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP jsdContext::GetScriptsEnabled (bool *_rval) { ASSERT_VALID_EPHEMERAL; if (!mISCx) { *_rval = true; return NS_OK; } nsCOMPtr context = do_QueryInterface(mISCx); if (!context) return NS_ERROR_NO_INTERFACE; *_rval = context->GetScriptsEnabled(); return NS_OK; } NS_IMETHODIMP jsdContext::SetScriptsEnabled (bool _rval) { ASSERT_VALID_EPHEMERAL; if (!mISCx) { if (_rval) return NS_OK; return NS_ERROR_NO_INTERFACE; } nsCOMPtr context = do_QueryInterface(mISCx); if (!context) return NS_ERROR_NO_INTERFACE; context->SetScriptsEnabled(_rval, true); return NS_OK; } /* Stack Frames */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdStackFrame, jsdIStackFrame, jsdIEphemeral) jsdStackFrame::jsdStackFrame (JSDContext *aCx, JSDThreadState *aThreadState, JSDStackFrameInfo *aStackFrameInfo) : mCx(aCx), mThreadState(aThreadState), mStackFrameInfo(aStackFrameInfo) { DEBUG_CREATE ("jsdStackFrame", gFrameCount); mValid = (aCx && aThreadState && aStackFrameInfo); if (mValid) { mLiveListEntry.key = aStackFrameInfo; mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveStackFrames, &mLiveListEntry); } } jsdStackFrame::~jsdStackFrame() { DEBUG_DESTROY ("jsdStackFrame", gFrameCount); if (mValid) { /* call Invalidate() to take ourselves out of the live list */ Invalidate(); } } jsdIStackFrame * jsdStackFrame::FromPtr (JSDContext *aCx, JSDThreadState *aThreadState, JSDStackFrameInfo *aStackFrameInfo) { if (!aStackFrameInfo) return nullptr; jsdIStackFrame *rv; nsCOMPtr frame; nsCOMPtr eph = jsds_FindEphemeral (&gLiveStackFrames, reinterpret_cast(aStackFrameInfo)); if (eph) { frame = do_QueryInterface(eph); rv = frame; } else { rv = new jsdStackFrame (aCx, aThreadState, aStackFrameInfo); } NS_IF_ADDREF(rv); return rv; } NS_IMETHODIMP jsdStackFrame::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = false; jsds_RemoveEphemeral (&gLiveStackFrames, &mLiveListEntry); return NS_OK; } void jsdStackFrame::InvalidateAll() { if (gLiveStackFrames) jsds_InvalidateAllEphemerals (&gLiveStackFrames); } NS_IMETHODIMP jsdStackFrame::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetJSDThreadState(JSDThreadState **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mThreadState; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetJSDStackFrameInfo(JSDStackFrameInfo **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mStackFrameInfo; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsValid(bool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetCallingFrame(jsdIStackFrame **_rval) { ASSERT_VALID_EPHEMERAL; JSDStackFrameInfo *sfi = JSD_GetCallingStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdStackFrame::FromPtr (mCx, mThreadState, sfi); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetExecutionContext(jsdIContext **_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetJSContext (mCx, mThreadState); *_rval = jsdContext::FromPtr (mCx, cx); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetFunctionName(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; JSString *str = JSD_GetIdForStackFrame(mCx, mThreadState, mStackFrameInfo); if (str) return AssignToJSString(mCx, &_rval, str); _rval.Assign("anonymous"); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsDebugger(bool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsStackFrameDebugger (mCx, mThreadState, mStackFrameInfo); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsConstructing(bool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsStackFrameConstructing (mCx, mThreadState, mStackFrameInfo); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetScript(jsdIScript **_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdScript::FromPtr (mCx, script); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetPc(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); if (!script) return NS_ERROR_FAILURE; uintptr_t pcbase = JSD_GetClosestPC(mCx, script, 0); uintptr_t pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); if (pc) *_rval = pc - pcbase; else *_rval = pcbase; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetLine(uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); if (script) { uintptr_t pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = JSD_GetClosestLine (mCx, script, pc); } else { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetCallee(jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetCallObjectForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetScope(jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetScopeChainForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetThisValue(jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetThisForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::Eval (const nsAString &bytes, const nsACString &fileName, uint32_t line, jsdIValue **result, bool *_rval) { ASSERT_VALID_EPHEMERAL; if (bytes.IsEmpty()) return NS_ERROR_INVALID_ARG; // get pointer to buffer contained in |bytes| nsAString::const_iterator h; bytes.BeginReading(h); const jschar *char_bytes = reinterpret_cast(h.get()); JSExceptionState *estate = 0; JSContext *cx = JSD_GetJSContext (mCx, mThreadState); JS::RootedValue jv(cx); estate = JS_SaveExceptionState (cx); JS_ClearPendingException (cx); nsCxPusher pusher; pusher.Push(cx); *_rval = JSD_AttemptUCScriptInStackFrame (mCx, mThreadState, mStackFrameInfo, char_bytes, bytes.Length(), PromiseFlatCString(fileName).get(), line, &jv); if (!*_rval) { if (JS_IsExceptionPending(cx)) JS_GetPendingException (cx, jv.address()); else jv = JSVAL_NULL; } JS_RestoreExceptionState (cx, estate); JSDValue *jsdv = JSD_NewValue (mCx, jv); if (!jsdv) return NS_ERROR_FAILURE; *result = jsdValue::FromPtr (mCx, jsdv); if (!*result) return NS_ERROR_FAILURE; return NS_OK; } /* Values */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdValue, jsdIValue, jsdIEphemeral) jsdIValue * jsdValue::FromPtr (JSDContext *aCx, JSDValue *aValue) { /* value will be dropped by te jsdValue destructor. */ if (!aValue) return nullptr; jsdIValue *rv = new jsdValue (aCx, aValue); NS_IF_ADDREF(rv); return rv; } jsdValue::jsdValue (JSDContext *aCx, JSDValue *aValue) : mValid(true), mCx(aCx), mValue(aValue) { DEBUG_CREATE ("jsdValue", gValueCount); mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveValues, &mLiveListEntry); } jsdValue::~jsdValue() { DEBUG_DESTROY ("jsdValue", gValueCount); if (mValid) /* call Invalidate() to take ourselves out of the live list */ Invalidate(); } NS_IMETHODIMP jsdValue::GetIsValid(bool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdValue::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = false; jsds_RemoveEphemeral (&gLiveValues, &mLiveListEntry); JSD_DropValue (mCx, mValue); return NS_OK; } void jsdValue::InvalidateAll() { if (gLiveValues) jsds_InvalidateAllEphemerals (&gLiveValues); } NS_IMETHODIMP jsdValue::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdValue::GetJSDValue (JSDValue **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mValue; return NS_OK; } NS_IMETHODIMP jsdValue::GetIsNative (bool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsValueNative (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIsNumber (bool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsValueNumber (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIsPrimitive (bool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsValuePrimitive (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsType (uint32_t *_rval) { ASSERT_VALID_EPHEMERAL; jsval val; val = JSD_GetValueWrappedJSVal (mCx, mValue); if (JSVAL_IS_NULL(val)) *_rval = TYPE_NULL; else if (JSVAL_IS_BOOLEAN(val)) *_rval = TYPE_BOOLEAN; else if (JSVAL_IS_DOUBLE(val)) *_rval = TYPE_DOUBLE; else if (JSVAL_IS_INT(val)) *_rval = TYPE_INT; else if (JSVAL_IS_STRING(val)) *_rval = TYPE_STRING; else if (JSVAL_IS_VOID(val)) *_rval = TYPE_VOID; else if (JSD_IsValueFunction (mCx, mValue)) *_rval = TYPE_FUNCTION; else if (!JSVAL_IS_PRIMITIVE(val)) *_rval = TYPE_OBJECT; else NS_ASSERTION (0, "Value has no discernible type."); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsPrototype (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetValuePrototype (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsParent (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetValueParent (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsClassName(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; _rval.Assign(JSD_GetValueClassName(mCx, mValue)); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsConstructor (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetValueConstructor (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsFunctionName(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; return AssignToJSString(mCx, &_rval, JSD_GetValueFunctionId(mCx, mValue)); } NS_IMETHODIMP jsdValue::GetBooleanValue(bool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetValueBoolean (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetDoubleValue(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetValueDouble (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIntValue(int32_t *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetValueInt (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetObjectValue(jsdIObject **_rval) { ASSERT_VALID_EPHEMERAL; JSDObject *obj; obj = JSD_GetObjectForValue (mCx, mValue); *_rval = jsdObject::FromPtr (mCx, obj); if (!*_rval) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP jsdValue::GetStringValue(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!cx) { NS_WARNING("No default context !?"); return NS_ERROR_FAILURE; } JSString *jstr_val = JSD_GetValueString(mCx, mValue); if (jstr_val) { size_t length; const jschar *chars = JS_GetStringCharsZAndLength(cx, jstr_val, &length); if (!chars) return NS_ERROR_FAILURE; nsDependentString depStr(chars, length); CopyUTF16toUTF8(depStr, _rval); } else { _rval.Truncate(); } return NS_OK; } NS_IMETHODIMP jsdValue::GetPropertyCount (int32_t *_rval) { ASSERT_VALID_EPHEMERAL; if (JSD_IsValueObject(mCx, mValue)) *_rval = JSD_GetCountOfProperties (mCx, mValue); else *_rval = -1; return NS_OK; } NS_IMETHODIMP jsdValue::GetProperties (jsdIProperty ***propArray, uint32_t *length) { ASSERT_VALID_EPHEMERAL; *propArray = nullptr; if (length) *length = 0; uint32_t prop_count = JSD_IsValueObject(mCx, mValue) ? JSD_GetCountOfProperties (mCx, mValue) : 0; NS_ENSURE_TRUE(prop_count, NS_OK); jsdIProperty **pa_temp = static_cast (nsMemory::Alloc(sizeof (jsdIProperty *) * prop_count)); NS_ENSURE_TRUE(pa_temp, NS_ERROR_OUT_OF_MEMORY); uint32_t i = 0; JSDProperty *iter = NULL; JSDProperty *prop; while ((prop = JSD_IterateProperties (mCx, mValue, &iter))) { pa_temp[i] = jsdProperty::FromPtr (mCx, prop); ++i; } NS_ASSERTION (prop_count == i, "property count mismatch"); /* if caller doesn't care about length, don't bother telling them */ *propArray = pa_temp; if (length) *length = prop_count; return NS_OK; } NS_IMETHODIMP jsdValue::GetProperty (const nsACString &name, jsdIProperty **_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); /* not rooting this */ JSString *jstr_name = JS_NewStringCopyZ(cx, PromiseFlatCString(name).get()); if (!jstr_name) return NS_ERROR_OUT_OF_MEMORY; JSDProperty *prop = JSD_GetValueProperty (mCx, mValue, jstr_name); *_rval = jsdProperty::FromPtr (mCx, prop); return NS_OK; } NS_IMETHODIMP jsdValue::Refresh() { ASSERT_VALID_EPHEMERAL; JSD_RefreshValue (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetWrappedValue(JSContext* aCx, JS::Value* aRetval) { ASSERT_VALID_EPHEMERAL; *aRetval = JSD_GetValueWrappedJSVal(mCx, mValue); if (!JS_WrapValue(aCx, aRetval)) { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP jsdValue::GetScript(jsdIScript **_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForValue(mCx, mValue); *_rval = jsdScript::FromPtr(mCx, script); return NS_OK; } /****************************************************************************** * debugger service implementation ******************************************************************************/ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(jsdService) NS_INTERFACE_MAP_ENTRY(jsdIDebuggerService) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, jsdIDebuggerService) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_10(jsdService, mErrorHook, mBreakpointHook, mDebugHook, mDebuggerHook, mInterruptHook, mScriptHook, mThrowHook, mTopLevelHook, mFunctionHook, mActivationCallback) NS_IMPL_CYCLE_COLLECTING_ADDREF(jsdService) NS_IMPL_CYCLE_COLLECTING_RELEASE(jsdService) NS_IMETHODIMP jsdService::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdService::GetFlags (uint32_t *_rval) { ASSERT_VALID_CONTEXT; *_rval = JSD_GetContextFlags (mCx); return NS_OK; } NS_IMETHODIMP jsdService::SetFlags (uint32_t flags) { ASSERT_VALID_CONTEXT; JSD_SetContextFlags (mCx, flags); return NS_OK; } NS_IMETHODIMP jsdService::GetImplementationString(nsACString &aImplementationString) { aImplementationString.AssignLiteral(implementationString); return NS_OK; } NS_IMETHODIMP jsdService::GetImplementationMajor(uint32_t *_rval) { *_rval = JSDS_MAJOR_VERSION; return NS_OK; } NS_IMETHODIMP jsdService::GetImplementationMinor(uint32_t *_rval) { *_rval = JSDS_MINOR_VERSION; return NS_OK; } NS_IMETHODIMP jsdService::GetIsOn (bool *_rval) { *_rval = mOn; return NS_OK; } NS_IMETHODIMP jsdService::On (void) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP jsdService::AsyncOn (jsdIActivationCallback *activationCallback) { nsresult rv; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); if (NS_FAILED(rv)) return rv; mActivationCallback = activationCallback; return xpc->SetDebugModeWhenPossible(true, true); } NS_IMETHODIMP jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, bool mode) { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); /* XPConnect now does this work itself, so this IDL entry point is no longer used. */ return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP jsdService::DeactivateDebugger () { if (!mCx) return NS_OK; jsdContext::InvalidateAll(); jsdScript::InvalidateAll(); jsdValue::InvalidateAll(); jsdProperty::InvalidateAll(); jsdStackFrame::InvalidateAll(); ClearAllBreakpoints(); JSD_SetErrorReporter (mCx, NULL, NULL); JSD_SetScriptHook (mCx, NULL, NULL); JSD_ClearThrowHook (mCx); JSD_ClearInterruptHook (mCx); JSD_ClearDebuggerHook (mCx); JSD_ClearDebugBreakHook (mCx); JSD_ClearTopLevelHook (mCx); JSD_ClearFunctionHook (mCx); JSD_DebuggerOff (mCx); mCx = nullptr; mRuntime = nullptr; mOn = false; return NS_OK; } NS_IMETHODIMP jsdService::ActivateDebugger (JSRuntime *rt) { if (mOn) return (rt == mRuntime) ? NS_OK : NS_ERROR_ALREADY_INITIALIZED; mRuntime = rt; if (gPrevGCSliceCallback == jsds_GCSliceCallbackProc) /* condition indicates that the callback proc has not been set yet */ gPrevGCSliceCallback = JS::SetGCSliceCallback (rt, jsds_GCSliceCallbackProc); mCx = JSD_DebuggerOnForUser (rt, NULL, NULL); if (!mCx) return NS_ERROR_FAILURE; JSContext *cx = JSD_GetDefaultJSContext (mCx); JS::RootedObject glob(cx, JSD_GetDefaultGlobal (mCx)); /* init xpconnect on the debugger's context in case xpconnect tries to * use it for stuff. */ nsresult rv; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); if (NS_FAILED(rv)) return rv; xpc->InitClasses (cx, glob); /* Start watching for script creation/destruction and manage jsdScript * objects accordingly */ JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); /* If any of these mFooHook objects are installed, do the required JSD * hookup now. See also, jsdService::SetFooHook(). */ if (mErrorHook) JSD_SetErrorReporter (mCx, jsds_ErrorHookProc, NULL); if (mThrowHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); /* can't ignore script callbacks, as we need to |Release| the wrapper * stored in private data when a script is deleted. */ if (mInterruptHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebuggerHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebugHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); if (mTopLevelHook) JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearTopLevelHook (mCx); if (mFunctionHook) JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearFunctionHook (mCx); mOn = true; #ifdef DEBUG printf ("+++ JavaScript debugging hooks installed.\n"); #endif nsCOMPtr activationCallback; mActivationCallback.swap(activationCallback); if (activationCallback) return activationCallback->OnDebuggerActivated(); return NS_OK; } NS_IMETHODIMP jsdService::Off (void) { if (!mOn) return NS_OK; if (!mCx || !mRuntime) return NS_ERROR_NOT_INITIALIZED; if (gDeadScripts) { if (gGCRunning) return NS_ERROR_NOT_AVAILABLE; JSContext *cx = JSD_GetDefaultJSContext(mCx); while (gDeadScripts) jsds_NotifyPendingDeadScripts (JS_GetRuntime(cx)); } DeactivateDebugger(); #ifdef DEBUG printf ("+++ JavaScript debugging hooks removed.\n"); #endif nsresult rv; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); if (NS_FAILED(rv)) return rv; xpc->SetDebugModeWhenPossible(false, true); return NS_OK; } NS_IMETHODIMP jsdService::GetPauseDepth(uint32_t *_rval) { NS_ENSURE_ARG_POINTER(_rval); *_rval = mPauseLevel; return NS_OK; } NS_IMETHODIMP jsdService::Pause(uint32_t *_rval) { return DoPause(_rval, false); } nsresult jsdService::DoPause(uint32_t *_rval, bool internalCall) { if (!mCx) return NS_ERROR_NOT_INITIALIZED; if (++mPauseLevel == 1) { JSD_SetErrorReporter (mCx, NULL, NULL); JSD_ClearThrowHook (mCx); JSD_ClearInterruptHook (mCx); JSD_ClearDebuggerHook (mCx); JSD_ClearDebugBreakHook (mCx); JSD_ClearTopLevelHook (mCx); JSD_ClearFunctionHook (mCx); JSD_DebuggerPause (mCx); nsresult rv; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); if (NS_FAILED(rv)) return rv; if (!internalCall) { rv = xpc->SetDebugModeWhenPossible(false, false); NS_ENSURE_SUCCESS(rv, rv); } } if (_rval) *_rval = mPauseLevel; return NS_OK; } NS_IMETHODIMP jsdService::UnPause(uint32_t *_rval) { return DoUnPause(_rval, false); } nsresult jsdService::DoUnPause(uint32_t *_rval, bool internalCall) { if (!mCx) return NS_ERROR_NOT_INITIALIZED; if (mPauseLevel == 0) return NS_ERROR_NOT_AVAILABLE; /* check mOn before we muck with this stuff, it's possible the debugger * was turned off while we were paused. */ if (--mPauseLevel == 0 && mOn) { JSD_DebuggerUnpause (mCx); if (mErrorHook) JSD_SetErrorReporter (mCx, jsds_ErrorHookProc, NULL); if (mThrowHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); if (mInterruptHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebuggerHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebugHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); if (mTopLevelHook) JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearTopLevelHook (mCx); if (mFunctionHook) JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearFunctionHook (mCx); nsresult rv; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); if (NS_FAILED(rv)) return rv; if (!internalCall) { rv = xpc->SetDebugModeWhenPossible(true, false); NS_ENSURE_SUCCESS(rv, rv); } } if (_rval) *_rval = mPauseLevel; return NS_OK; } NS_IMETHODIMP jsdService::EnumerateContexts (jsdIContextEnumerator *enumerator) { ASSERT_VALID_CONTEXT; if (!enumerator) return NS_OK; JSContext *iter = NULL; JSContext *cx; while ((cx = JS_ContextIterator (mRuntime, &iter))) { nsCOMPtr jsdicx = getter_AddRefs(jsdContext::FromPtr(mCx, cx)); if (jsdicx) { if (NS_FAILED(enumerator->EnumerateContext(jsdicx))) break; } } return NS_OK; } NS_IMETHODIMP jsdService::EnumerateScripts (jsdIScriptEnumerator *enumerator) { ASSERT_VALID_CONTEXT; JSDScript *script; JSDScript *iter = NULL; nsresult rv = NS_OK; JSD_LockScriptSubsystem(mCx); while((script = JSD_IterateScripts(mCx, &iter))) { nsCOMPtr jsdis = getter_AddRefs(jsdScript::FromPtr(mCx, script)); rv = enumerator->EnumerateScript (jsdis); if (NS_FAILED(rv)) break; } JSD_UnlockScriptSubsystem(mCx); return rv; } NS_IMETHODIMP jsdService::GC (void) { ASSERT_VALID_CONTEXT; JSRuntime *rt = JSD_GetJSRuntime (mCx); JS_GC(rt); return NS_OK; } NS_IMETHODIMP jsdService::DumpHeap(const nsACString &fileName) { ASSERT_VALID_CONTEXT; #ifndef DEBUG return NS_ERROR_NOT_IMPLEMENTED; #else nsresult rv = NS_OK; FILE *file = !fileName.IsEmpty() ? fopen(PromiseFlatCString(fileName).get(), "w") : stdout; if (!file) { rv = NS_ERROR_FAILURE; } else { JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!JS_DumpHeap(JS_GetRuntime(cx), file, NULL, JSTRACE_OBJECT, NULL, (size_t)-1, NULL)) rv = NS_ERROR_FAILURE; if (file != stdout) fclose(file); } return rv; #endif } NS_IMETHODIMP jsdService::ClearProfileData () { ASSERT_VALID_CONTEXT; JSD_ClearAllProfileData (mCx); return NS_OK; } NS_IMETHODIMP jsdService::InsertFilter (jsdIFilter *filter, jsdIFilter *after) { NS_ENSURE_ARG_POINTER (filter); if (jsds_FindFilter (filter)) return NS_ERROR_INVALID_ARG; FilterRecord *rec = PR_NEWZAP (FilterRecord); if (!rec) return NS_ERROR_OUT_OF_MEMORY; if (!jsds_SyncFilter (rec, filter)) { PR_Free (rec); return NS_ERROR_FAILURE; } if (gFilters) { if (!after) { /* insert at head of list */ PR_INSERT_LINK(&rec->links, &gFilters->links); gFilters = rec; } else { /* insert somewhere in the list */ FilterRecord *afterRecord = jsds_FindFilter (after); if (!afterRecord) { jsds_FreeFilter(rec); return NS_ERROR_INVALID_ARG; } PR_INSERT_AFTER(&rec->links, &afterRecord->links); } } else { if (after) { /* user asked to insert into the middle of an empty list, bail. */ jsds_FreeFilter(rec); return NS_ERROR_NOT_INITIALIZED; } PR_INIT_CLIST(&rec->links); gFilters = rec; } return NS_OK; } NS_IMETHODIMP jsdService::AppendFilter (jsdIFilter *filter) { NS_ENSURE_ARG_POINTER (filter); if (jsds_FindFilter (filter)) return NS_ERROR_INVALID_ARG; FilterRecord *rec = PR_NEWZAP (FilterRecord); if (!jsds_SyncFilter (rec, filter)) { PR_Free (rec); return NS_ERROR_FAILURE; } if (gFilters) { PR_INSERT_BEFORE(&rec->links, &gFilters->links); } else { PR_INIT_CLIST(&rec->links); gFilters = rec; } return NS_OK; } NS_IMETHODIMP jsdService::RemoveFilter (jsdIFilter *filter) { NS_ENSURE_ARG_POINTER(filter); FilterRecord *rec = jsds_FindFilter (filter); if (!rec) return NS_ERROR_INVALID_ARG; if (gFilters == rec) { gFilters = reinterpret_cast (PR_NEXT_LINK(&rec->links)); /* If we're the only filter left, null out the list head. */ if (gFilters == rec) gFilters = nullptr; } PR_REMOVE_LINK(&rec->links); jsds_FreeFilter (rec); return NS_OK; } NS_IMETHODIMP jsdService::SwapFilters (jsdIFilter *filter_a, jsdIFilter *filter_b) { NS_ENSURE_ARG_POINTER(filter_a); NS_ENSURE_ARG_POINTER(filter_b); FilterRecord *rec_a = jsds_FindFilter (filter_a); if (!rec_a) return NS_ERROR_INVALID_ARG; if (filter_a == filter_b) { /* just a refresh */ if (!jsds_SyncFilter (rec_a, filter_a)) return NS_ERROR_FAILURE; return NS_OK; } FilterRecord *rec_b = jsds_FindFilter (filter_b); if (!rec_b) { /* filter_b is not in the list, replace filter_a with filter_b. */ if (!jsds_SyncFilter (rec_a, filter_b)) return NS_ERROR_FAILURE; } else { /* both filters are in the list, swap. */ if (!jsds_SyncFilter (rec_a, filter_b)) return NS_ERROR_FAILURE; if (!jsds_SyncFilter (rec_b, filter_a)) return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP jsdService::EnumerateFilters (jsdIFilterEnumerator *enumerator) { if (!gFilters) return NS_OK; FilterRecord *current = gFilters; do { jsds_SyncFilter (current, current->filterObject); /* SyncFilter failure would be bad, but what would we do about it? */ if (enumerator) { nsresult rv = enumerator->EnumerateFilter (current->filterObject); if (NS_FAILED(rv)) return rv; } current = reinterpret_cast (PR_NEXT_LINK (¤t->links)); } while (current != gFilters); return NS_OK; } NS_IMETHODIMP jsdService::RefreshFilters () { return EnumerateFilters(nullptr); } NS_IMETHODIMP jsdService::ClearFilters () { if (!gFilters) return NS_OK; FilterRecord *current = reinterpret_cast (PR_NEXT_LINK (&gFilters->links)); do { FilterRecord *next = reinterpret_cast (PR_NEXT_LINK (¤t->links)); PR_REMOVE_AND_INIT_LINK(¤t->links); jsds_FreeFilter(current); current = next; } while (current != gFilters); jsds_FreeFilter(current); gFilters = nullptr; return NS_OK; } NS_IMETHODIMP jsdService::ClearAllBreakpoints (void) { ASSERT_VALID_CONTEXT; JSD_LockScriptSubsystem(mCx); JSD_ClearAllExecutionHooks (mCx); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } NS_IMETHODIMP jsdService::WrapValue(const JS::Value &value, jsdIValue **_rval) { ASSERT_VALID_CONTEXT; JSDValue *jsdv = JSD_NewValue(mCx, value); if (!jsdv) return NS_ERROR_FAILURE; *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdService::EnterNestedEventLoop (jsdINestCallback *callback, uint32_t *_rval) { // Nesting event queues is a thing of the past. Now, we just spin the // current event loop. nsresult rv = NS_OK; nsCxPusher pusher; pusher.PushNull(); uint32_t nestLevel = ++mNestedLoopLevel; nsCOMPtr thread = do_GetCurrentThread(); if (callback) { DoPause(nullptr, true); rv = callback->OnNest(); DoUnPause(nullptr, true); } while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) { if (!NS_ProcessNextEvent(thread)) rv = NS_ERROR_UNEXPECTED; } NS_ASSERTION (mNestedLoopLevel <= nestLevel, "nested event didn't unwind properly"); if (mNestedLoopLevel == nestLevel) --mNestedLoopLevel; *_rval = mNestedLoopLevel; return rv; } NS_IMETHODIMP jsdService::ExitNestedEventLoop (uint32_t *_rval) { if (mNestedLoopLevel > 0) --mNestedLoopLevel; else return NS_ERROR_FAILURE; *_rval = mNestedLoopLevel; return NS_OK; } /* hook attribute get/set functions */ NS_IMETHODIMP jsdService::SetErrorHook (jsdIErrorHook *aHook) { mErrorHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetErrorReporter (mCx, jsds_ErrorHookProc, NULL); else JSD_SetErrorReporter (mCx, NULL, NULL); return NS_OK; } NS_IMETHODIMP jsdService::GetErrorHook (jsdIErrorHook **aHook) { *aHook = mErrorHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetBreakpointHook (jsdIExecutionHook *aHook) { mBreakpointHook = aHook; return NS_OK; } NS_IMETHODIMP jsdService::GetBreakpointHook (jsdIExecutionHook **aHook) { *aHook = mBreakpointHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetDebugHook (jsdIExecutionHook *aHook) { mDebugHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearDebugBreakHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetDebugHook (jsdIExecutionHook **aHook) { *aHook = mDebugHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetDebuggerHook (jsdIExecutionHook *aHook) { mDebuggerHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearDebuggerHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetDebuggerHook (jsdIExecutionHook **aHook) { *aHook = mDebuggerHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetInterruptHook (jsdIExecutionHook *aHook) { mInterruptHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearInterruptHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetInterruptHook (jsdIExecutionHook **aHook) { *aHook = mInterruptHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetScriptHook (jsdIScriptHook *aHook) { mScriptHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); /* we can't unset it if !aHook, because we still need to see script * deletes in order to Release the jsdIScripts held in JSDScript * private data. */ return NS_OK; } NS_IMETHODIMP jsdService::GetScriptHook (jsdIScriptHook **aHook) { *aHook = mScriptHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetThrowHook (jsdIExecutionHook *aHook) { mThrowHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearThrowHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetThrowHook (jsdIExecutionHook **aHook) { *aHook = mThrowHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetTopLevelHook (jsdICallHook *aHook) { mTopLevelHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearTopLevelHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetTopLevelHook (jsdICallHook **aHook) { *aHook = mTopLevelHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetFunctionHook (jsdICallHook *aHook) { mFunctionHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * ActivateDebugger() method will do the rest when the coast is clear. */ if (!mCx || mPauseLevel) return NS_OK; if (aHook) JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearFunctionHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetFunctionHook (jsdICallHook **aHook) { *aHook = mFunctionHook; NS_IF_ADDREF(*aHook); return NS_OK; } /* virtual */ jsdService::~jsdService() { ClearFilters(); mErrorHook = nullptr; mBreakpointHook = nullptr; mDebugHook = nullptr; mDebuggerHook = nullptr; mInterruptHook = nullptr; mScriptHook = nullptr; mThrowHook = nullptr; mTopLevelHook = nullptr; mFunctionHook = nullptr; Off(); gJsds = nullptr; } jsdService * jsdService::GetService () { if (!gJsds) gJsds = new jsdService(); NS_IF_ADDREF(gJsds); return gJsds; } NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(jsdService, jsdService::GetService) /* app-start observer. turns on the debugger at app-start. this is inserted * and/or removed from the app-start category by the jsdService::initAtStartup * property. */ class jsdASObserver MOZ_FINAL : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER jsdASObserver () {} }; NS_IMPL_THREADSAFE_ISUPPORTS1(jsdASObserver, nsIObserver) NS_IMETHODIMP jsdASObserver::Observe (nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) { nsresult rv; // Hmm. Why is the app-startup observer called multiple times? //NS_ASSERTION(!gJsds, "app startup observer called twice"); nsCOMPtr jsds = do_GetService(jsdServiceCtrID, &rv); if (NS_FAILED(rv)) return rv; bool on; rv = jsds->GetIsOn(&on); if (NS_FAILED(rv) || on) return rv; nsCOMPtr rts = do_GetService(NS_JSRT_CTRID, &rv); if (NS_FAILED(rv)) return rv; JSRuntime *rt; rts->GetRuntime (&rt); if (NS_FAILED(rv)) return rv; rv = jsds->ActivateDebugger(rt); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_GENERIC_FACTORY_CONSTRUCTOR(jsdASObserver) NS_DEFINE_NAMED_CID(JSDSERVICE_CID); NS_DEFINE_NAMED_CID(JSDASO_CID); static const mozilla::Module::CIDEntry kJSDCIDs[] = { { &kJSDSERVICE_CID, false, NULL, jsdServiceConstructor }, { &kJSDASO_CID, false, NULL, jsdASObserverConstructor }, { NULL } }; static const mozilla::Module::ContractIDEntry kJSDContracts[] = { { jsdServiceCtrID, &kJSDSERVICE_CID }, { jsdARObserverCtrID, &kJSDASO_CID }, { NULL } }; static const mozilla::Module kJSDModule = { mozilla::Module::kVersion, kJSDCIDs, kJSDContracts }; NSMODULE_DEFN(JavaScript_Debugger) = &kJSDModule; void global_finalize(JSFreeOp *aFop, JSObject *aObj) { nsIScriptObjectPrincipal *sop = static_cast(js::GetObjectPrivate(aObj)); MOZ_ASSERT(sop); static_cast(sop)->ForgetGlobalObject(); NS_IF_RELEASE(sop); } JSObject * CreateJSDGlobal(JSContext *aCx, JSClass *aClasp) { nsresult rv; nsCOMPtr nullPrin = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); NS_ENSURE_SUCCESS(rv, nullptr); JSPrincipals *jsPrin = nsJSPrincipals::get(nullPrin); JSObject *global = JS_NewGlobalObject(aCx, aClasp, jsPrin); NS_ENSURE_TRUE(global, nullptr); // We have created a new global let's attach a private to it // that implements nsIGlobalObject. nsCOMPtr sbp = new SandboxPrivate(nullPrin, global); JS_SetPrivate(global, sbp.forget().get()); return global; } /******************************************************************************** ******************************************************************************** * graveyard */ #if 0 /* Thread States */ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdThreadState, jsdIThreadState); NS_IMETHODIMP jsdThreadState::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdThreadState::GetJSDThreadState(JSDThreadState **_rval) { *_rval = mThreadState; return NS_OK; } NS_IMETHODIMP jsdThreadState::GetFrameCount (uint32_t *_rval) { *_rval = JSD_GetCountOfStackFrames (mCx, mThreadState); return NS_OK; } NS_IMETHODIMP jsdThreadState::GetTopFrame (jsdIStackFrame **_rval) { JSDStackFrameInfo *sfi = JSD_GetStackFrame (mCx, mThreadState); *_rval = jsdStackFrame::FromPtr (mCx, mThreadState, sfi); return NS_OK; } NS_IMETHODIMP jsdThreadState::GetPendingException(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetException (mCx, mThreadState); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdThreadState::SetPendingException(jsdIValue *aException) { JSDValue *jsdv; nsresult rv = aException->GetJSDValue (&jsdv); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; if (!JSD_SetException (mCx, mThreadState, jsdv)) return NS_ERROR_FAILURE; return NS_OK; } #endif