michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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: #include "mozilla/dom/Exceptions.h" michael@0: michael@0: #include "js/GCAPI.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "jsapi.h" michael@0: #include "jsprf.h" michael@0: #include "mozilla/CycleCollectedJSRuntime.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/DOMException.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "XPCWrapper.h" michael@0: #include "WorkerPrivate.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: namespace { michael@0: michael@0: // We can't use nsContentUtils::IsCallerChrome because it might not exist in michael@0: // xpcshell. michael@0: bool michael@0: IsCallerChrome() michael@0: { michael@0: nsCOMPtr secMan; michael@0: secMan = XPCWrapper::GetSecurityManager(); michael@0: michael@0: if (!secMan) { michael@0: return false; michael@0: } michael@0: michael@0: bool isChrome; michael@0: return NS_SUCCEEDED(secMan->SubjectPrincipalIsSystem(&isChrome)) && isChrome; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: bool michael@0: ThrowExceptionObject(JSContext* aCx, nsIException* aException) michael@0: { michael@0: // See if we really have an Exception. michael@0: nsCOMPtr exception = do_QueryInterface(aException); michael@0: if (exception) { michael@0: return ThrowExceptionObject(aCx, exception); michael@0: } michael@0: michael@0: // We only have an nsIException (probably an XPCWrappedJS). Fall back on old michael@0: // wrapping. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: JS::Rooted glob(aCx, JS::CurrentGlobalOrNull(aCx)); michael@0: if (!glob) { michael@0: // XXXbz Can this really be null here? michael@0: return false; michael@0: } michael@0: michael@0: JS::Rooted val(aCx); michael@0: if (!WrapObject(aCx, aException, &NS_GET_IID(nsIException), &val)) { michael@0: return false; michael@0: } michael@0: michael@0: JS_SetPendingException(aCx, val); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ThrowExceptionObject(JSContext* aCx, Exception* aException) michael@0: { michael@0: JS::Rooted thrown(aCx); michael@0: michael@0: // If we stored the original thrown JS value in the exception michael@0: // (see XPCConvert::ConstructException) and we are in a web context michael@0: // (i.e., not chrome), rethrow the original value. This only applies to JS michael@0: // implemented components so we only need to check for this on the main michael@0: // thread. michael@0: if (NS_IsMainThread() && !IsCallerChrome() && michael@0: aException->StealJSVal(thrown.address())) { michael@0: if (!JS_WrapValue(aCx, &thrown)) { michael@0: return false; michael@0: } michael@0: JS_SetPendingException(aCx, thrown); michael@0: return true; michael@0: } michael@0: michael@0: JS::Rooted glob(aCx, JS::CurrentGlobalOrNull(aCx)); michael@0: if (!glob) { michael@0: // XXXbz Can this actually be null here? michael@0: return false; michael@0: } michael@0: michael@0: if (!WrapNewBindingObject(aCx, aException, &thrown)) { michael@0: return false; michael@0: } michael@0: michael@0: JS_SetPendingException(aCx, thrown); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Throw(JSContext* aCx, nsresult aRv, const char* aMessage) michael@0: { michael@0: if (JS_IsExceptionPending(aCx)) { michael@0: // Don't clobber the existing exception. michael@0: return false; michael@0: } michael@0: michael@0: CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); michael@0: nsCOMPtr existingException = runtime->GetPendingException(); michael@0: if (existingException) { michael@0: nsresult nr; michael@0: if (NS_SUCCEEDED(existingException->GetResult(&nr)) && michael@0: aRv == nr) { michael@0: // Reuse the existing exception. michael@0: michael@0: // Clear pending exception michael@0: runtime->SetPendingException(nullptr); michael@0: michael@0: if (!ThrowExceptionObject(aCx, existingException)) { michael@0: // If we weren't able to throw an exception we're michael@0: // most likely out of memory michael@0: JS_ReportOutOfMemory(aCx); michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr finalException = CreateException(aCx, aRv, aMessage); michael@0: michael@0: MOZ_ASSERT(finalException); michael@0: if (!ThrowExceptionObject(aCx, finalException)) { michael@0: // If we weren't able to throw an exception we're michael@0: // most likely out of memory michael@0: JS_ReportOutOfMemory(aCx); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: already_AddRefed michael@0: CreateException(JSContext* aCx, nsresult aRv, const char* aMessage) michael@0: { michael@0: // Do we use DOM exceptions for this error code? michael@0: switch (NS_ERROR_GET_MODULE(aRv)) { michael@0: case NS_ERROR_MODULE_DOM: michael@0: case NS_ERROR_MODULE_SVG: michael@0: case NS_ERROR_MODULE_DOM_XPATH: michael@0: case NS_ERROR_MODULE_DOM_INDEXEDDB: michael@0: case NS_ERROR_MODULE_DOM_FILEHANDLE: michael@0: return DOMException::Create(aRv); michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // If not, use the default. michael@0: // aMessage can be null, so we can't use nsDependentCString on it. michael@0: nsRefPtr exception = michael@0: new Exception(nsCString(aMessage), aRv, michael@0: EmptyCString(), nullptr, nullptr); michael@0: return exception.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: GetCurrentJSStack() michael@0: { michael@0: // is there a current context available? michael@0: JSContext* cx = nullptr; michael@0: michael@0: if (NS_IsMainThread()) { michael@0: // Note, in xpcshell nsContentUtils is never initialized, but we still need michael@0: // to report exceptions. michael@0: if (nsContentUtils::XPConnect()) { michael@0: cx = nsContentUtils::XPConnect()->GetCurrentJSContext(); michael@0: } else { michael@0: nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); michael@0: cx = xpc->GetCurrentJSContext(); michael@0: } michael@0: } else { michael@0: cx = workers::GetCurrentThreadJSContext(); michael@0: } michael@0: michael@0: if (!cx) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr stack = exceptions::CreateStack(cx); michael@0: if (!stack) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // peel off native frames... michael@0: uint32_t language; michael@0: nsCOMPtr caller; michael@0: while (stack && michael@0: NS_SUCCEEDED(stack->GetLanguage(&language)) && michael@0: language != nsIProgrammingLanguage::JAVASCRIPT && michael@0: NS_SUCCEEDED(stack->GetCaller(getter_AddRefs(caller))) && michael@0: caller) { michael@0: stack = caller; michael@0: } michael@0: return stack.forget(); michael@0: } michael@0: michael@0: namespace exceptions { michael@0: michael@0: class StackDescriptionOwner { michael@0: public: michael@0: StackDescriptionOwner(JS::StackDescription* aDescription) michael@0: : mDescription(aDescription) michael@0: { michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: ~StackDescriptionOwner() michael@0: { michael@0: // Make sure to set mDescription to null before calling DropJSObjects, since michael@0: // in debug builds DropJSObjects try to trace us and we don't want to trace michael@0: // a dead StackDescription. michael@0: if (mDescription) { michael@0: JS::FreeStackDescription(nullptr, mDescription); michael@0: mDescription = nullptr; michael@0: } michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(StackDescriptionOwner) michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(StackDescriptionOwner) michael@0: michael@0: JS::FrameDescription& FrameAt(size_t aIndex) michael@0: { michael@0: MOZ_ASSERT(aIndex < mDescription->nframes); michael@0: return mDescription->frames[aIndex]; michael@0: } michael@0: michael@0: unsigned NumFrames() michael@0: { michael@0: return mDescription->nframes; michael@0: } michael@0: michael@0: private: michael@0: JS::StackDescription* mDescription; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(StackDescriptionOwner, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(StackDescriptionOwner, Release) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(StackDescriptionOwner) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StackDescriptionOwner) michael@0: if (tmp->mDescription) { michael@0: JS::FreeStackDescription(nullptr, tmp->mDescription); michael@0: tmp->mDescription = nullptr; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StackDescriptionOwner) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(StackDescriptionOwner) michael@0: JS::StackDescription* desc = tmp->mDescription; michael@0: if (tmp->mDescription) { michael@0: for (size_t i = 0; i < desc->nframes; ++i) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].markedLocation1()); michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].markedLocation2()); michael@0: } michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: class JSStackFrame : public nsIStackFrame michael@0: { michael@0: public: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_CLASS(JSStackFrame) michael@0: NS_DECL_NSISTACKFRAME michael@0: michael@0: // A null aStackDescription or an aIndex that's out of range for the michael@0: // number of frames aStackDescription has will mean that the michael@0: // JSStackFrame will never look at the stack description. Instead, michael@0: // it is expected to be initialized by the caller as needed. michael@0: JSStackFrame(StackDescriptionOwner* aStackDescription, size_t aIndex); michael@0: virtual ~JSStackFrame(); michael@0: michael@0: static already_AddRefed michael@0: CreateStack(JSContext* aCx, int32_t aMaxDepth = -1); michael@0: static already_AddRefed michael@0: CreateStackFrameLocation(uint32_t aLanguage, michael@0: const char* aFilename, michael@0: const char* aFunctionName, michael@0: int32_t aLineNumber, michael@0: nsIStackFrame* aCaller); michael@0: michael@0: private: michael@0: bool IsJSFrame() const { michael@0: return mLanguage == nsIProgrammingLanguage::JAVASCRIPT; michael@0: } michael@0: michael@0: int32_t GetLineno(); michael@0: michael@0: nsRefPtr mStackDescription; michael@0: nsCOMPtr mCaller; michael@0: michael@0: // Cached values michael@0: nsString mFilename; michael@0: nsString mFunname; michael@0: int32_t mLineno; michael@0: uint32_t mLanguage; michael@0: michael@0: size_t mIndex; michael@0: michael@0: bool mFilenameInitialized; michael@0: bool mFunnameInitialized; michael@0: bool mLinenoInitialized; michael@0: bool mCallerInitialized; michael@0: }; michael@0: michael@0: JSStackFrame::JSStackFrame(StackDescriptionOwner* aStackDescription, michael@0: size_t aIndex) michael@0: : mLineno(0) michael@0: { michael@0: if (aStackDescription && aIndex < aStackDescription->NumFrames()) { michael@0: mStackDescription = aStackDescription; michael@0: mIndex = aIndex; michael@0: mFilenameInitialized = false; michael@0: mFunnameInitialized = false; michael@0: mLinenoInitialized = false; michael@0: mCallerInitialized = false; michael@0: mLanguage = nsIProgrammingLanguage::JAVASCRIPT; michael@0: } else { michael@0: MOZ_ASSERT(!mStackDescription); michael@0: mIndex = 0; michael@0: mFilenameInitialized = true; michael@0: mFunnameInitialized = true; michael@0: mLinenoInitialized = true; michael@0: mCallerInitialized = true; michael@0: mLanguage = nsIProgrammingLanguage::UNKNOWN; michael@0: } michael@0: } michael@0: michael@0: JSStackFrame::~JSStackFrame() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(JSStackFrame, mStackDescription, mCaller) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(JSStackFrame) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(JSStackFrame) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSStackFrame) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStackFrame) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: /* readonly attribute uint32_t language; */ michael@0: NS_IMETHODIMP JSStackFrame::GetLanguage(uint32_t* aLanguage) michael@0: { michael@0: *aLanguage = mLanguage; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute string languageName; */ michael@0: NS_IMETHODIMP JSStackFrame::GetLanguageName(nsACString& aLanguageName) michael@0: { michael@0: static const char js[] = "JavaScript"; michael@0: static const char cpp[] = "C++"; michael@0: michael@0: if (IsJSFrame()) { michael@0: aLanguageName.AssignASCII(js); michael@0: } else { michael@0: aLanguageName.AssignASCII(cpp); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute AString filename; */ michael@0: NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename) michael@0: { michael@0: if (!mFilenameInitialized) { michael@0: JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); michael@0: if (const char *filename = desc.filename()) { michael@0: CopyUTF8toUTF16(filename, mFilename); michael@0: } michael@0: mFilenameInitialized = true; michael@0: } michael@0: michael@0: // The filename must be set to null if empty. michael@0: if (mFilename.IsEmpty()) { michael@0: aFilename.SetIsVoid(true); michael@0: } else { michael@0: aFilename.Assign(mFilename); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute AString name; */ michael@0: NS_IMETHODIMP JSStackFrame::GetName(nsAString& aFunction) michael@0: { michael@0: if (!mFunnameInitialized) { michael@0: JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); michael@0: if (JSFlatString *name = desc.funDisplayName()) { michael@0: mFunname.Assign(JS_GetFlatStringChars(name), michael@0: // XXXbz Can't JS_GetStringLength on JSFlatString! michael@0: JS_GetStringLength(JS_FORGET_STRING_FLATNESS(name))); michael@0: } michael@0: mFunnameInitialized = true; michael@0: } michael@0: michael@0: // The function name must be set to null if empty. michael@0: if (mFunname.IsEmpty()) { michael@0: aFunction.SetIsVoid(true); michael@0: } else { michael@0: aFunction.Assign(mFunname); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t michael@0: JSStackFrame::GetLineno() michael@0: { michael@0: if (!mLinenoInitialized) { michael@0: JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); michael@0: mLineno = desc.lineno(); michael@0: mLinenoInitialized = true; michael@0: } michael@0: michael@0: return mLineno; michael@0: } michael@0: michael@0: /* readonly attribute int32_t lineNumber; */ michael@0: NS_IMETHODIMP JSStackFrame::GetLineNumber(int32_t* aLineNumber) michael@0: { michael@0: *aLineNumber = GetLineno(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute AUTF8String sourceLine; */ michael@0: NS_IMETHODIMP JSStackFrame::GetSourceLine(nsACString& aSourceLine) michael@0: { michael@0: aSourceLine.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsIStackFrame caller; */ michael@0: NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller) michael@0: { michael@0: if (!mCallerInitialized) { michael@0: mCaller = new JSStackFrame(mStackDescription, mIndex+1); michael@0: mCallerInitialized = true; michael@0: } michael@0: NS_IF_ADDREF(*aCaller = mCaller); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* AUTF8String toString (); */ michael@0: NS_IMETHODIMP JSStackFrame::ToString(nsACString& _retval) michael@0: { michael@0: _retval.Truncate(); michael@0: michael@0: const char* frametype = IsJSFrame() ? "JS" : "native"; michael@0: michael@0: nsString filename; michael@0: nsresult rv = GetFilename(filename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (filename.IsEmpty()) { michael@0: filename.AssignLiteral(""); michael@0: } michael@0: michael@0: nsString funname; michael@0: rv = GetName(funname); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (funname.IsEmpty()) { michael@0: funname.AssignLiteral(""); michael@0: } michael@0: static const char format[] = "%s frame :: %s :: %s :: line %d"; michael@0: _retval.AppendPrintf(format, frametype, michael@0: NS_ConvertUTF16toUTF8(filename).get(), michael@0: NS_ConvertUTF16toUTF8(funname).get(), michael@0: GetLineno()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth) michael@0: { michael@0: static const unsigned MAX_FRAMES = 100; michael@0: if (aMaxDepth < 0) { michael@0: aMaxDepth = MAX_FRAMES; michael@0: } michael@0: michael@0: JS::StackDescription* desc = JS::DescribeStack(aCx, aMaxDepth); michael@0: if (!desc) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr descOwner = new StackDescriptionOwner(desc); michael@0: michael@0: nsRefPtr first = new JSStackFrame(descOwner, 0); michael@0: return first.forget(); michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: JSStackFrame::CreateStackFrameLocation(uint32_t aLanguage, michael@0: const char* aFilename, michael@0: const char* aFunctionName, michael@0: int32_t aLineNumber, michael@0: nsIStackFrame* aCaller) michael@0: { michael@0: nsRefPtr self = new JSStackFrame(nullptr, 0); michael@0: michael@0: self->mLanguage = aLanguage; michael@0: self->mLineno = aLineNumber; michael@0: CopyUTF8toUTF16(aFilename, self->mFilename); michael@0: CopyUTF8toUTF16(aFunctionName, self->mFunname); michael@0: michael@0: self->mCaller = aCaller; michael@0: michael@0: return self.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CreateStack(JSContext* aCx, int32_t aMaxDepth) michael@0: { michael@0: return JSStackFrame::CreateStack(aCx, aMaxDepth); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CreateStackFrameLocation(uint32_t aLanguage, michael@0: const char* aFilename, michael@0: const char* aFunctionName, michael@0: int32_t aLineNumber, michael@0: nsIStackFrame* aCaller) michael@0: { michael@0: return JSStackFrame::CreateStackFrameLocation(aLanguage, aFilename, michael@0: aFunctionName, aLineNumber, michael@0: aCaller); michael@0: } michael@0: michael@0: } // namespace exceptions michael@0: } // namespace dom michael@0: } // namespace mozilla