1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/bindings/Exceptions.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,548 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/dom/Exceptions.h" 1.10 + 1.11 +#include "js/GCAPI.h" 1.12 +#include "js/OldDebugAPI.h" 1.13 +#include "jsapi.h" 1.14 +#include "jsprf.h" 1.15 +#include "mozilla/CycleCollectedJSRuntime.h" 1.16 +#include "mozilla/dom/BindingUtils.h" 1.17 +#include "mozilla/dom/DOMException.h" 1.18 +#include "nsServiceManagerUtils.h" 1.19 +#include "nsThreadUtils.h" 1.20 +#include "XPCWrapper.h" 1.21 +#include "WorkerPrivate.h" 1.22 +#include "nsContentUtils.h" 1.23 + 1.24 +namespace { 1.25 + 1.26 +// We can't use nsContentUtils::IsCallerChrome because it might not exist in 1.27 +// xpcshell. 1.28 +bool 1.29 +IsCallerChrome() 1.30 +{ 1.31 + nsCOMPtr<nsIScriptSecurityManager> secMan; 1.32 + secMan = XPCWrapper::GetSecurityManager(); 1.33 + 1.34 + if (!secMan) { 1.35 + return false; 1.36 + } 1.37 + 1.38 + bool isChrome; 1.39 + return NS_SUCCEEDED(secMan->SubjectPrincipalIsSystem(&isChrome)) && isChrome; 1.40 +} 1.41 + 1.42 +} // anonymous namespace 1.43 + 1.44 +namespace mozilla { 1.45 +namespace dom { 1.46 + 1.47 +bool 1.48 +ThrowExceptionObject(JSContext* aCx, nsIException* aException) 1.49 +{ 1.50 + // See if we really have an Exception. 1.51 + nsCOMPtr<Exception> exception = do_QueryInterface(aException); 1.52 + if (exception) { 1.53 + return ThrowExceptionObject(aCx, exception); 1.54 + } 1.55 + 1.56 + // We only have an nsIException (probably an XPCWrappedJS). Fall back on old 1.57 + // wrapping. 1.58 + MOZ_ASSERT(NS_IsMainThread()); 1.59 + 1.60 + JS::Rooted<JSObject*> glob(aCx, JS::CurrentGlobalOrNull(aCx)); 1.61 + if (!glob) { 1.62 + // XXXbz Can this really be null here? 1.63 + return false; 1.64 + } 1.65 + 1.66 + JS::Rooted<JS::Value> val(aCx); 1.67 + if (!WrapObject(aCx, aException, &NS_GET_IID(nsIException), &val)) { 1.68 + return false; 1.69 + } 1.70 + 1.71 + JS_SetPendingException(aCx, val); 1.72 + 1.73 + return true; 1.74 +} 1.75 + 1.76 +bool 1.77 +ThrowExceptionObject(JSContext* aCx, Exception* aException) 1.78 +{ 1.79 + JS::Rooted<JS::Value> thrown(aCx); 1.80 + 1.81 + // If we stored the original thrown JS value in the exception 1.82 + // (see XPCConvert::ConstructException) and we are in a web context 1.83 + // (i.e., not chrome), rethrow the original value. This only applies to JS 1.84 + // implemented components so we only need to check for this on the main 1.85 + // thread. 1.86 + if (NS_IsMainThread() && !IsCallerChrome() && 1.87 + aException->StealJSVal(thrown.address())) { 1.88 + if (!JS_WrapValue(aCx, &thrown)) { 1.89 + return false; 1.90 + } 1.91 + JS_SetPendingException(aCx, thrown); 1.92 + return true; 1.93 + } 1.94 + 1.95 + JS::Rooted<JSObject*> glob(aCx, JS::CurrentGlobalOrNull(aCx)); 1.96 + if (!glob) { 1.97 + // XXXbz Can this actually be null here? 1.98 + return false; 1.99 + } 1.100 + 1.101 + if (!WrapNewBindingObject(aCx, aException, &thrown)) { 1.102 + return false; 1.103 + } 1.104 + 1.105 + JS_SetPendingException(aCx, thrown); 1.106 + return true; 1.107 +} 1.108 + 1.109 +bool 1.110 +Throw(JSContext* aCx, nsresult aRv, const char* aMessage) 1.111 +{ 1.112 + if (JS_IsExceptionPending(aCx)) { 1.113 + // Don't clobber the existing exception. 1.114 + return false; 1.115 + } 1.116 + 1.117 + CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); 1.118 + nsCOMPtr<nsIException> existingException = runtime->GetPendingException(); 1.119 + if (existingException) { 1.120 + nsresult nr; 1.121 + if (NS_SUCCEEDED(existingException->GetResult(&nr)) && 1.122 + aRv == nr) { 1.123 + // Reuse the existing exception. 1.124 + 1.125 + // Clear pending exception 1.126 + runtime->SetPendingException(nullptr); 1.127 + 1.128 + if (!ThrowExceptionObject(aCx, existingException)) { 1.129 + // If we weren't able to throw an exception we're 1.130 + // most likely out of memory 1.131 + JS_ReportOutOfMemory(aCx); 1.132 + } 1.133 + return false; 1.134 + } 1.135 + } 1.136 + 1.137 + nsRefPtr<Exception> finalException = CreateException(aCx, aRv, aMessage); 1.138 + 1.139 + MOZ_ASSERT(finalException); 1.140 + if (!ThrowExceptionObject(aCx, finalException)) { 1.141 + // If we weren't able to throw an exception we're 1.142 + // most likely out of memory 1.143 + JS_ReportOutOfMemory(aCx); 1.144 + } 1.145 + 1.146 + return false; 1.147 +} 1.148 + 1.149 +already_AddRefed<Exception> 1.150 +CreateException(JSContext* aCx, nsresult aRv, const char* aMessage) 1.151 +{ 1.152 + // Do we use DOM exceptions for this error code? 1.153 + switch (NS_ERROR_GET_MODULE(aRv)) { 1.154 + case NS_ERROR_MODULE_DOM: 1.155 + case NS_ERROR_MODULE_SVG: 1.156 + case NS_ERROR_MODULE_DOM_XPATH: 1.157 + case NS_ERROR_MODULE_DOM_INDEXEDDB: 1.158 + case NS_ERROR_MODULE_DOM_FILEHANDLE: 1.159 + return DOMException::Create(aRv); 1.160 + default: 1.161 + break; 1.162 + } 1.163 + 1.164 + // If not, use the default. 1.165 + // aMessage can be null, so we can't use nsDependentCString on it. 1.166 + nsRefPtr<Exception> exception = 1.167 + new Exception(nsCString(aMessage), aRv, 1.168 + EmptyCString(), nullptr, nullptr); 1.169 + return exception.forget(); 1.170 +} 1.171 + 1.172 +already_AddRefed<nsIStackFrame> 1.173 +GetCurrentJSStack() 1.174 +{ 1.175 + // is there a current context available? 1.176 + JSContext* cx = nullptr; 1.177 + 1.178 + if (NS_IsMainThread()) { 1.179 + // Note, in xpcshell nsContentUtils is never initialized, but we still need 1.180 + // to report exceptions. 1.181 + if (nsContentUtils::XPConnect()) { 1.182 + cx = nsContentUtils::XPConnect()->GetCurrentJSContext(); 1.183 + } else { 1.184 + nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID()); 1.185 + cx = xpc->GetCurrentJSContext(); 1.186 + } 1.187 + } else { 1.188 + cx = workers::GetCurrentThreadJSContext(); 1.189 + } 1.190 + 1.191 + if (!cx) { 1.192 + return nullptr; 1.193 + } 1.194 + 1.195 + nsCOMPtr<nsIStackFrame> stack = exceptions::CreateStack(cx); 1.196 + if (!stack) { 1.197 + return nullptr; 1.198 + } 1.199 + 1.200 + // peel off native frames... 1.201 + uint32_t language; 1.202 + nsCOMPtr<nsIStackFrame> caller; 1.203 + while (stack && 1.204 + NS_SUCCEEDED(stack->GetLanguage(&language)) && 1.205 + language != nsIProgrammingLanguage::JAVASCRIPT && 1.206 + NS_SUCCEEDED(stack->GetCaller(getter_AddRefs(caller))) && 1.207 + caller) { 1.208 + stack = caller; 1.209 + } 1.210 + return stack.forget(); 1.211 +} 1.212 + 1.213 +namespace exceptions { 1.214 + 1.215 +class StackDescriptionOwner { 1.216 +public: 1.217 + StackDescriptionOwner(JS::StackDescription* aDescription) 1.218 + : mDescription(aDescription) 1.219 + { 1.220 + mozilla::HoldJSObjects(this); 1.221 + } 1.222 + 1.223 + ~StackDescriptionOwner() 1.224 + { 1.225 + // Make sure to set mDescription to null before calling DropJSObjects, since 1.226 + // in debug builds DropJSObjects try to trace us and we don't want to trace 1.227 + // a dead StackDescription. 1.228 + if (mDescription) { 1.229 + JS::FreeStackDescription(nullptr, mDescription); 1.230 + mDescription = nullptr; 1.231 + } 1.232 + mozilla::DropJSObjects(this); 1.233 + } 1.234 + 1.235 + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(StackDescriptionOwner) 1.236 + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(StackDescriptionOwner) 1.237 + 1.238 + JS::FrameDescription& FrameAt(size_t aIndex) 1.239 + { 1.240 + MOZ_ASSERT(aIndex < mDescription->nframes); 1.241 + return mDescription->frames[aIndex]; 1.242 + } 1.243 + 1.244 + unsigned NumFrames() 1.245 + { 1.246 + return mDescription->nframes; 1.247 + } 1.248 + 1.249 +private: 1.250 + JS::StackDescription* mDescription; 1.251 +}; 1.252 + 1.253 +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(StackDescriptionOwner, AddRef) 1.254 +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(StackDescriptionOwner, Release) 1.255 + 1.256 +NS_IMPL_CYCLE_COLLECTION_CLASS(StackDescriptionOwner) 1.257 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StackDescriptionOwner) 1.258 + if (tmp->mDescription) { 1.259 + JS::FreeStackDescription(nullptr, tmp->mDescription); 1.260 + tmp->mDescription = nullptr; 1.261 + } 1.262 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.263 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StackDescriptionOwner) 1.264 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 1.265 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.266 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(StackDescriptionOwner) 1.267 + JS::StackDescription* desc = tmp->mDescription; 1.268 + if (tmp->mDescription) { 1.269 + for (size_t i = 0; i < desc->nframes; ++i) { 1.270 + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].markedLocation1()); 1.271 + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].markedLocation2()); 1.272 + } 1.273 + } 1.274 +NS_IMPL_CYCLE_COLLECTION_TRACE_END 1.275 + 1.276 +class JSStackFrame : public nsIStackFrame 1.277 +{ 1.278 +public: 1.279 + NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1.280 + NS_DECL_CYCLE_COLLECTION_CLASS(JSStackFrame) 1.281 + NS_DECL_NSISTACKFRAME 1.282 + 1.283 + // A null aStackDescription or an aIndex that's out of range for the 1.284 + // number of frames aStackDescription has will mean that the 1.285 + // JSStackFrame will never look at the stack description. Instead, 1.286 + // it is expected to be initialized by the caller as needed. 1.287 + JSStackFrame(StackDescriptionOwner* aStackDescription, size_t aIndex); 1.288 + virtual ~JSStackFrame(); 1.289 + 1.290 + static already_AddRefed<nsIStackFrame> 1.291 + CreateStack(JSContext* aCx, int32_t aMaxDepth = -1); 1.292 + static already_AddRefed<nsIStackFrame> 1.293 + CreateStackFrameLocation(uint32_t aLanguage, 1.294 + const char* aFilename, 1.295 + const char* aFunctionName, 1.296 + int32_t aLineNumber, 1.297 + nsIStackFrame* aCaller); 1.298 + 1.299 +private: 1.300 + bool IsJSFrame() const { 1.301 + return mLanguage == nsIProgrammingLanguage::JAVASCRIPT; 1.302 + } 1.303 + 1.304 + int32_t GetLineno(); 1.305 + 1.306 + nsRefPtr<StackDescriptionOwner> mStackDescription; 1.307 + nsCOMPtr<nsIStackFrame> mCaller; 1.308 + 1.309 + // Cached values 1.310 + nsString mFilename; 1.311 + nsString mFunname; 1.312 + int32_t mLineno; 1.313 + uint32_t mLanguage; 1.314 + 1.315 + size_t mIndex; 1.316 + 1.317 + bool mFilenameInitialized; 1.318 + bool mFunnameInitialized; 1.319 + bool mLinenoInitialized; 1.320 + bool mCallerInitialized; 1.321 +}; 1.322 + 1.323 +JSStackFrame::JSStackFrame(StackDescriptionOwner* aStackDescription, 1.324 + size_t aIndex) 1.325 + : mLineno(0) 1.326 +{ 1.327 + if (aStackDescription && aIndex < aStackDescription->NumFrames()) { 1.328 + mStackDescription = aStackDescription; 1.329 + mIndex = aIndex; 1.330 + mFilenameInitialized = false; 1.331 + mFunnameInitialized = false; 1.332 + mLinenoInitialized = false; 1.333 + mCallerInitialized = false; 1.334 + mLanguage = nsIProgrammingLanguage::JAVASCRIPT; 1.335 + } else { 1.336 + MOZ_ASSERT(!mStackDescription); 1.337 + mIndex = 0; 1.338 + mFilenameInitialized = true; 1.339 + mFunnameInitialized = true; 1.340 + mLinenoInitialized = true; 1.341 + mCallerInitialized = true; 1.342 + mLanguage = nsIProgrammingLanguage::UNKNOWN; 1.343 + } 1.344 +} 1.345 + 1.346 +JSStackFrame::~JSStackFrame() 1.347 +{ 1.348 +} 1.349 + 1.350 +NS_IMPL_CYCLE_COLLECTION(JSStackFrame, mStackDescription, mCaller) 1.351 + 1.352 +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSStackFrame) 1.353 +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSStackFrame) 1.354 + 1.355 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSStackFrame) 1.356 + NS_INTERFACE_MAP_ENTRY(nsIStackFrame) 1.357 + NS_INTERFACE_MAP_ENTRY(nsISupports) 1.358 +NS_INTERFACE_MAP_END 1.359 + 1.360 +/* readonly attribute uint32_t language; */ 1.361 +NS_IMETHODIMP JSStackFrame::GetLanguage(uint32_t* aLanguage) 1.362 +{ 1.363 + *aLanguage = mLanguage; 1.364 + return NS_OK; 1.365 +} 1.366 + 1.367 +/* readonly attribute string languageName; */ 1.368 +NS_IMETHODIMP JSStackFrame::GetLanguageName(nsACString& aLanguageName) 1.369 +{ 1.370 + static const char js[] = "JavaScript"; 1.371 + static const char cpp[] = "C++"; 1.372 + 1.373 + if (IsJSFrame()) { 1.374 + aLanguageName.AssignASCII(js); 1.375 + } else { 1.376 + aLanguageName.AssignASCII(cpp); 1.377 + } 1.378 + 1.379 + return NS_OK; 1.380 +} 1.381 + 1.382 +/* readonly attribute AString filename; */ 1.383 +NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename) 1.384 +{ 1.385 + if (!mFilenameInitialized) { 1.386 + JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); 1.387 + if (const char *filename = desc.filename()) { 1.388 + CopyUTF8toUTF16(filename, mFilename); 1.389 + } 1.390 + mFilenameInitialized = true; 1.391 + } 1.392 + 1.393 + // The filename must be set to null if empty. 1.394 + if (mFilename.IsEmpty()) { 1.395 + aFilename.SetIsVoid(true); 1.396 + } else { 1.397 + aFilename.Assign(mFilename); 1.398 + } 1.399 + 1.400 + return NS_OK; 1.401 +} 1.402 + 1.403 +/* readonly attribute AString name; */ 1.404 +NS_IMETHODIMP JSStackFrame::GetName(nsAString& aFunction) 1.405 +{ 1.406 + if (!mFunnameInitialized) { 1.407 + JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); 1.408 + if (JSFlatString *name = desc.funDisplayName()) { 1.409 + mFunname.Assign(JS_GetFlatStringChars(name), 1.410 + // XXXbz Can't JS_GetStringLength on JSFlatString! 1.411 + JS_GetStringLength(JS_FORGET_STRING_FLATNESS(name))); 1.412 + } 1.413 + mFunnameInitialized = true; 1.414 + } 1.415 + 1.416 + // The function name must be set to null if empty. 1.417 + if (mFunname.IsEmpty()) { 1.418 + aFunction.SetIsVoid(true); 1.419 + } else { 1.420 + aFunction.Assign(mFunname); 1.421 + } 1.422 + 1.423 + return NS_OK; 1.424 +} 1.425 + 1.426 +int32_t 1.427 +JSStackFrame::GetLineno() 1.428 +{ 1.429 + if (!mLinenoInitialized) { 1.430 + JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); 1.431 + mLineno = desc.lineno(); 1.432 + mLinenoInitialized = true; 1.433 + } 1.434 + 1.435 + return mLineno; 1.436 +} 1.437 + 1.438 +/* readonly attribute int32_t lineNumber; */ 1.439 +NS_IMETHODIMP JSStackFrame::GetLineNumber(int32_t* aLineNumber) 1.440 +{ 1.441 + *aLineNumber = GetLineno(); 1.442 + return NS_OK; 1.443 +} 1.444 + 1.445 +/* readonly attribute AUTF8String sourceLine; */ 1.446 +NS_IMETHODIMP JSStackFrame::GetSourceLine(nsACString& aSourceLine) 1.447 +{ 1.448 + aSourceLine.Truncate(); 1.449 + return NS_OK; 1.450 +} 1.451 + 1.452 +/* readonly attribute nsIStackFrame caller; */ 1.453 +NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller) 1.454 +{ 1.455 + if (!mCallerInitialized) { 1.456 + mCaller = new JSStackFrame(mStackDescription, mIndex+1); 1.457 + mCallerInitialized = true; 1.458 + } 1.459 + NS_IF_ADDREF(*aCaller = mCaller); 1.460 + return NS_OK; 1.461 +} 1.462 + 1.463 +/* AUTF8String toString (); */ 1.464 +NS_IMETHODIMP JSStackFrame::ToString(nsACString& _retval) 1.465 +{ 1.466 + _retval.Truncate(); 1.467 + 1.468 + const char* frametype = IsJSFrame() ? "JS" : "native"; 1.469 + 1.470 + nsString filename; 1.471 + nsresult rv = GetFilename(filename); 1.472 + NS_ENSURE_SUCCESS(rv, rv); 1.473 + 1.474 + if (filename.IsEmpty()) { 1.475 + filename.AssignLiteral("<unknown filename>"); 1.476 + } 1.477 + 1.478 + nsString funname; 1.479 + rv = GetName(funname); 1.480 + NS_ENSURE_SUCCESS(rv, rv); 1.481 + 1.482 + if (funname.IsEmpty()) { 1.483 + funname.AssignLiteral("<TOP_LEVEL>"); 1.484 + } 1.485 + static const char format[] = "%s frame :: %s :: %s :: line %d"; 1.486 + _retval.AppendPrintf(format, frametype, 1.487 + NS_ConvertUTF16toUTF8(filename).get(), 1.488 + NS_ConvertUTF16toUTF8(funname).get(), 1.489 + GetLineno()); 1.490 + return NS_OK; 1.491 +} 1.492 + 1.493 +/* static */ already_AddRefed<nsIStackFrame> 1.494 +JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth) 1.495 +{ 1.496 + static const unsigned MAX_FRAMES = 100; 1.497 + if (aMaxDepth < 0) { 1.498 + aMaxDepth = MAX_FRAMES; 1.499 + } 1.500 + 1.501 + JS::StackDescription* desc = JS::DescribeStack(aCx, aMaxDepth); 1.502 + if (!desc) { 1.503 + return nullptr; 1.504 + } 1.505 + 1.506 + nsRefPtr<StackDescriptionOwner> descOwner = new StackDescriptionOwner(desc); 1.507 + 1.508 + nsRefPtr<JSStackFrame> first = new JSStackFrame(descOwner, 0); 1.509 + return first.forget(); 1.510 +} 1.511 + 1.512 +/* static */ already_AddRefed<nsIStackFrame> 1.513 +JSStackFrame::CreateStackFrameLocation(uint32_t aLanguage, 1.514 + const char* aFilename, 1.515 + const char* aFunctionName, 1.516 + int32_t aLineNumber, 1.517 + nsIStackFrame* aCaller) 1.518 +{ 1.519 + nsRefPtr<JSStackFrame> self = new JSStackFrame(nullptr, 0); 1.520 + 1.521 + self->mLanguage = aLanguage; 1.522 + self->mLineno = aLineNumber; 1.523 + CopyUTF8toUTF16(aFilename, self->mFilename); 1.524 + CopyUTF8toUTF16(aFunctionName, self->mFunname); 1.525 + 1.526 + self->mCaller = aCaller; 1.527 + 1.528 + return self.forget(); 1.529 +} 1.530 + 1.531 +already_AddRefed<nsIStackFrame> 1.532 +CreateStack(JSContext* aCx, int32_t aMaxDepth) 1.533 +{ 1.534 + return JSStackFrame::CreateStack(aCx, aMaxDepth); 1.535 +} 1.536 + 1.537 +already_AddRefed<nsIStackFrame> 1.538 +CreateStackFrameLocation(uint32_t aLanguage, 1.539 + const char* aFilename, 1.540 + const char* aFunctionName, 1.541 + int32_t aLineNumber, 1.542 + nsIStackFrame* aCaller) 1.543 +{ 1.544 + return JSStackFrame::CreateStackFrameLocation(aLanguage, aFilename, 1.545 + aFunctionName, aLineNumber, 1.546 + aCaller); 1.547 +} 1.548 + 1.549 +} // namespace exceptions 1.550 +} // namespace dom 1.551 +} // namespace mozilla