|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "mozilla/dom/Exceptions.h" |
|
7 |
|
8 #include "js/GCAPI.h" |
|
9 #include "js/OldDebugAPI.h" |
|
10 #include "jsapi.h" |
|
11 #include "jsprf.h" |
|
12 #include "mozilla/CycleCollectedJSRuntime.h" |
|
13 #include "mozilla/dom/BindingUtils.h" |
|
14 #include "mozilla/dom/DOMException.h" |
|
15 #include "nsServiceManagerUtils.h" |
|
16 #include "nsThreadUtils.h" |
|
17 #include "XPCWrapper.h" |
|
18 #include "WorkerPrivate.h" |
|
19 #include "nsContentUtils.h" |
|
20 |
|
21 namespace { |
|
22 |
|
23 // We can't use nsContentUtils::IsCallerChrome because it might not exist in |
|
24 // xpcshell. |
|
25 bool |
|
26 IsCallerChrome() |
|
27 { |
|
28 nsCOMPtr<nsIScriptSecurityManager> secMan; |
|
29 secMan = XPCWrapper::GetSecurityManager(); |
|
30 |
|
31 if (!secMan) { |
|
32 return false; |
|
33 } |
|
34 |
|
35 bool isChrome; |
|
36 return NS_SUCCEEDED(secMan->SubjectPrincipalIsSystem(&isChrome)) && isChrome; |
|
37 } |
|
38 |
|
39 } // anonymous namespace |
|
40 |
|
41 namespace mozilla { |
|
42 namespace dom { |
|
43 |
|
44 bool |
|
45 ThrowExceptionObject(JSContext* aCx, nsIException* aException) |
|
46 { |
|
47 // See if we really have an Exception. |
|
48 nsCOMPtr<Exception> exception = do_QueryInterface(aException); |
|
49 if (exception) { |
|
50 return ThrowExceptionObject(aCx, exception); |
|
51 } |
|
52 |
|
53 // We only have an nsIException (probably an XPCWrappedJS). Fall back on old |
|
54 // wrapping. |
|
55 MOZ_ASSERT(NS_IsMainThread()); |
|
56 |
|
57 JS::Rooted<JSObject*> glob(aCx, JS::CurrentGlobalOrNull(aCx)); |
|
58 if (!glob) { |
|
59 // XXXbz Can this really be null here? |
|
60 return false; |
|
61 } |
|
62 |
|
63 JS::Rooted<JS::Value> val(aCx); |
|
64 if (!WrapObject(aCx, aException, &NS_GET_IID(nsIException), &val)) { |
|
65 return false; |
|
66 } |
|
67 |
|
68 JS_SetPendingException(aCx, val); |
|
69 |
|
70 return true; |
|
71 } |
|
72 |
|
73 bool |
|
74 ThrowExceptionObject(JSContext* aCx, Exception* aException) |
|
75 { |
|
76 JS::Rooted<JS::Value> thrown(aCx); |
|
77 |
|
78 // If we stored the original thrown JS value in the exception |
|
79 // (see XPCConvert::ConstructException) and we are in a web context |
|
80 // (i.e., not chrome), rethrow the original value. This only applies to JS |
|
81 // implemented components so we only need to check for this on the main |
|
82 // thread. |
|
83 if (NS_IsMainThread() && !IsCallerChrome() && |
|
84 aException->StealJSVal(thrown.address())) { |
|
85 if (!JS_WrapValue(aCx, &thrown)) { |
|
86 return false; |
|
87 } |
|
88 JS_SetPendingException(aCx, thrown); |
|
89 return true; |
|
90 } |
|
91 |
|
92 JS::Rooted<JSObject*> glob(aCx, JS::CurrentGlobalOrNull(aCx)); |
|
93 if (!glob) { |
|
94 // XXXbz Can this actually be null here? |
|
95 return false; |
|
96 } |
|
97 |
|
98 if (!WrapNewBindingObject(aCx, aException, &thrown)) { |
|
99 return false; |
|
100 } |
|
101 |
|
102 JS_SetPendingException(aCx, thrown); |
|
103 return true; |
|
104 } |
|
105 |
|
106 bool |
|
107 Throw(JSContext* aCx, nsresult aRv, const char* aMessage) |
|
108 { |
|
109 if (JS_IsExceptionPending(aCx)) { |
|
110 // Don't clobber the existing exception. |
|
111 return false; |
|
112 } |
|
113 |
|
114 CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); |
|
115 nsCOMPtr<nsIException> existingException = runtime->GetPendingException(); |
|
116 if (existingException) { |
|
117 nsresult nr; |
|
118 if (NS_SUCCEEDED(existingException->GetResult(&nr)) && |
|
119 aRv == nr) { |
|
120 // Reuse the existing exception. |
|
121 |
|
122 // Clear pending exception |
|
123 runtime->SetPendingException(nullptr); |
|
124 |
|
125 if (!ThrowExceptionObject(aCx, existingException)) { |
|
126 // If we weren't able to throw an exception we're |
|
127 // most likely out of memory |
|
128 JS_ReportOutOfMemory(aCx); |
|
129 } |
|
130 return false; |
|
131 } |
|
132 } |
|
133 |
|
134 nsRefPtr<Exception> finalException = CreateException(aCx, aRv, aMessage); |
|
135 |
|
136 MOZ_ASSERT(finalException); |
|
137 if (!ThrowExceptionObject(aCx, finalException)) { |
|
138 // If we weren't able to throw an exception we're |
|
139 // most likely out of memory |
|
140 JS_ReportOutOfMemory(aCx); |
|
141 } |
|
142 |
|
143 return false; |
|
144 } |
|
145 |
|
146 already_AddRefed<Exception> |
|
147 CreateException(JSContext* aCx, nsresult aRv, const char* aMessage) |
|
148 { |
|
149 // Do we use DOM exceptions for this error code? |
|
150 switch (NS_ERROR_GET_MODULE(aRv)) { |
|
151 case NS_ERROR_MODULE_DOM: |
|
152 case NS_ERROR_MODULE_SVG: |
|
153 case NS_ERROR_MODULE_DOM_XPATH: |
|
154 case NS_ERROR_MODULE_DOM_INDEXEDDB: |
|
155 case NS_ERROR_MODULE_DOM_FILEHANDLE: |
|
156 return DOMException::Create(aRv); |
|
157 default: |
|
158 break; |
|
159 } |
|
160 |
|
161 // If not, use the default. |
|
162 // aMessage can be null, so we can't use nsDependentCString on it. |
|
163 nsRefPtr<Exception> exception = |
|
164 new Exception(nsCString(aMessage), aRv, |
|
165 EmptyCString(), nullptr, nullptr); |
|
166 return exception.forget(); |
|
167 } |
|
168 |
|
169 already_AddRefed<nsIStackFrame> |
|
170 GetCurrentJSStack() |
|
171 { |
|
172 // is there a current context available? |
|
173 JSContext* cx = nullptr; |
|
174 |
|
175 if (NS_IsMainThread()) { |
|
176 // Note, in xpcshell nsContentUtils is never initialized, but we still need |
|
177 // to report exceptions. |
|
178 if (nsContentUtils::XPConnect()) { |
|
179 cx = nsContentUtils::XPConnect()->GetCurrentJSContext(); |
|
180 } else { |
|
181 nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID()); |
|
182 cx = xpc->GetCurrentJSContext(); |
|
183 } |
|
184 } else { |
|
185 cx = workers::GetCurrentThreadJSContext(); |
|
186 } |
|
187 |
|
188 if (!cx) { |
|
189 return nullptr; |
|
190 } |
|
191 |
|
192 nsCOMPtr<nsIStackFrame> stack = exceptions::CreateStack(cx); |
|
193 if (!stack) { |
|
194 return nullptr; |
|
195 } |
|
196 |
|
197 // peel off native frames... |
|
198 uint32_t language; |
|
199 nsCOMPtr<nsIStackFrame> caller; |
|
200 while (stack && |
|
201 NS_SUCCEEDED(stack->GetLanguage(&language)) && |
|
202 language != nsIProgrammingLanguage::JAVASCRIPT && |
|
203 NS_SUCCEEDED(stack->GetCaller(getter_AddRefs(caller))) && |
|
204 caller) { |
|
205 stack = caller; |
|
206 } |
|
207 return stack.forget(); |
|
208 } |
|
209 |
|
210 namespace exceptions { |
|
211 |
|
212 class StackDescriptionOwner { |
|
213 public: |
|
214 StackDescriptionOwner(JS::StackDescription* aDescription) |
|
215 : mDescription(aDescription) |
|
216 { |
|
217 mozilla::HoldJSObjects(this); |
|
218 } |
|
219 |
|
220 ~StackDescriptionOwner() |
|
221 { |
|
222 // Make sure to set mDescription to null before calling DropJSObjects, since |
|
223 // in debug builds DropJSObjects try to trace us and we don't want to trace |
|
224 // a dead StackDescription. |
|
225 if (mDescription) { |
|
226 JS::FreeStackDescription(nullptr, mDescription); |
|
227 mDescription = nullptr; |
|
228 } |
|
229 mozilla::DropJSObjects(this); |
|
230 } |
|
231 |
|
232 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(StackDescriptionOwner) |
|
233 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(StackDescriptionOwner) |
|
234 |
|
235 JS::FrameDescription& FrameAt(size_t aIndex) |
|
236 { |
|
237 MOZ_ASSERT(aIndex < mDescription->nframes); |
|
238 return mDescription->frames[aIndex]; |
|
239 } |
|
240 |
|
241 unsigned NumFrames() |
|
242 { |
|
243 return mDescription->nframes; |
|
244 } |
|
245 |
|
246 private: |
|
247 JS::StackDescription* mDescription; |
|
248 }; |
|
249 |
|
250 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(StackDescriptionOwner, AddRef) |
|
251 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(StackDescriptionOwner, Release) |
|
252 |
|
253 NS_IMPL_CYCLE_COLLECTION_CLASS(StackDescriptionOwner) |
|
254 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StackDescriptionOwner) |
|
255 if (tmp->mDescription) { |
|
256 JS::FreeStackDescription(nullptr, tmp->mDescription); |
|
257 tmp->mDescription = nullptr; |
|
258 } |
|
259 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
260 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StackDescriptionOwner) |
|
261 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
263 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(StackDescriptionOwner) |
|
264 JS::StackDescription* desc = tmp->mDescription; |
|
265 if (tmp->mDescription) { |
|
266 for (size_t i = 0; i < desc->nframes; ++i) { |
|
267 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].markedLocation1()); |
|
268 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].markedLocation2()); |
|
269 } |
|
270 } |
|
271 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
272 |
|
273 class JSStackFrame : public nsIStackFrame |
|
274 { |
|
275 public: |
|
276 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
277 NS_DECL_CYCLE_COLLECTION_CLASS(JSStackFrame) |
|
278 NS_DECL_NSISTACKFRAME |
|
279 |
|
280 // A null aStackDescription or an aIndex that's out of range for the |
|
281 // number of frames aStackDescription has will mean that the |
|
282 // JSStackFrame will never look at the stack description. Instead, |
|
283 // it is expected to be initialized by the caller as needed. |
|
284 JSStackFrame(StackDescriptionOwner* aStackDescription, size_t aIndex); |
|
285 virtual ~JSStackFrame(); |
|
286 |
|
287 static already_AddRefed<nsIStackFrame> |
|
288 CreateStack(JSContext* aCx, int32_t aMaxDepth = -1); |
|
289 static already_AddRefed<nsIStackFrame> |
|
290 CreateStackFrameLocation(uint32_t aLanguage, |
|
291 const char* aFilename, |
|
292 const char* aFunctionName, |
|
293 int32_t aLineNumber, |
|
294 nsIStackFrame* aCaller); |
|
295 |
|
296 private: |
|
297 bool IsJSFrame() const { |
|
298 return mLanguage == nsIProgrammingLanguage::JAVASCRIPT; |
|
299 } |
|
300 |
|
301 int32_t GetLineno(); |
|
302 |
|
303 nsRefPtr<StackDescriptionOwner> mStackDescription; |
|
304 nsCOMPtr<nsIStackFrame> mCaller; |
|
305 |
|
306 // Cached values |
|
307 nsString mFilename; |
|
308 nsString mFunname; |
|
309 int32_t mLineno; |
|
310 uint32_t mLanguage; |
|
311 |
|
312 size_t mIndex; |
|
313 |
|
314 bool mFilenameInitialized; |
|
315 bool mFunnameInitialized; |
|
316 bool mLinenoInitialized; |
|
317 bool mCallerInitialized; |
|
318 }; |
|
319 |
|
320 JSStackFrame::JSStackFrame(StackDescriptionOwner* aStackDescription, |
|
321 size_t aIndex) |
|
322 : mLineno(0) |
|
323 { |
|
324 if (aStackDescription && aIndex < aStackDescription->NumFrames()) { |
|
325 mStackDescription = aStackDescription; |
|
326 mIndex = aIndex; |
|
327 mFilenameInitialized = false; |
|
328 mFunnameInitialized = false; |
|
329 mLinenoInitialized = false; |
|
330 mCallerInitialized = false; |
|
331 mLanguage = nsIProgrammingLanguage::JAVASCRIPT; |
|
332 } else { |
|
333 MOZ_ASSERT(!mStackDescription); |
|
334 mIndex = 0; |
|
335 mFilenameInitialized = true; |
|
336 mFunnameInitialized = true; |
|
337 mLinenoInitialized = true; |
|
338 mCallerInitialized = true; |
|
339 mLanguage = nsIProgrammingLanguage::UNKNOWN; |
|
340 } |
|
341 } |
|
342 |
|
343 JSStackFrame::~JSStackFrame() |
|
344 { |
|
345 } |
|
346 |
|
347 NS_IMPL_CYCLE_COLLECTION(JSStackFrame, mStackDescription, mCaller) |
|
348 |
|
349 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSStackFrame) |
|
350 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSStackFrame) |
|
351 |
|
352 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSStackFrame) |
|
353 NS_INTERFACE_MAP_ENTRY(nsIStackFrame) |
|
354 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
355 NS_INTERFACE_MAP_END |
|
356 |
|
357 /* readonly attribute uint32_t language; */ |
|
358 NS_IMETHODIMP JSStackFrame::GetLanguage(uint32_t* aLanguage) |
|
359 { |
|
360 *aLanguage = mLanguage; |
|
361 return NS_OK; |
|
362 } |
|
363 |
|
364 /* readonly attribute string languageName; */ |
|
365 NS_IMETHODIMP JSStackFrame::GetLanguageName(nsACString& aLanguageName) |
|
366 { |
|
367 static const char js[] = "JavaScript"; |
|
368 static const char cpp[] = "C++"; |
|
369 |
|
370 if (IsJSFrame()) { |
|
371 aLanguageName.AssignASCII(js); |
|
372 } else { |
|
373 aLanguageName.AssignASCII(cpp); |
|
374 } |
|
375 |
|
376 return NS_OK; |
|
377 } |
|
378 |
|
379 /* readonly attribute AString filename; */ |
|
380 NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename) |
|
381 { |
|
382 if (!mFilenameInitialized) { |
|
383 JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); |
|
384 if (const char *filename = desc.filename()) { |
|
385 CopyUTF8toUTF16(filename, mFilename); |
|
386 } |
|
387 mFilenameInitialized = true; |
|
388 } |
|
389 |
|
390 // The filename must be set to null if empty. |
|
391 if (mFilename.IsEmpty()) { |
|
392 aFilename.SetIsVoid(true); |
|
393 } else { |
|
394 aFilename.Assign(mFilename); |
|
395 } |
|
396 |
|
397 return NS_OK; |
|
398 } |
|
399 |
|
400 /* readonly attribute AString name; */ |
|
401 NS_IMETHODIMP JSStackFrame::GetName(nsAString& aFunction) |
|
402 { |
|
403 if (!mFunnameInitialized) { |
|
404 JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); |
|
405 if (JSFlatString *name = desc.funDisplayName()) { |
|
406 mFunname.Assign(JS_GetFlatStringChars(name), |
|
407 // XXXbz Can't JS_GetStringLength on JSFlatString! |
|
408 JS_GetStringLength(JS_FORGET_STRING_FLATNESS(name))); |
|
409 } |
|
410 mFunnameInitialized = true; |
|
411 } |
|
412 |
|
413 // The function name must be set to null if empty. |
|
414 if (mFunname.IsEmpty()) { |
|
415 aFunction.SetIsVoid(true); |
|
416 } else { |
|
417 aFunction.Assign(mFunname); |
|
418 } |
|
419 |
|
420 return NS_OK; |
|
421 } |
|
422 |
|
423 int32_t |
|
424 JSStackFrame::GetLineno() |
|
425 { |
|
426 if (!mLinenoInitialized) { |
|
427 JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex); |
|
428 mLineno = desc.lineno(); |
|
429 mLinenoInitialized = true; |
|
430 } |
|
431 |
|
432 return mLineno; |
|
433 } |
|
434 |
|
435 /* readonly attribute int32_t lineNumber; */ |
|
436 NS_IMETHODIMP JSStackFrame::GetLineNumber(int32_t* aLineNumber) |
|
437 { |
|
438 *aLineNumber = GetLineno(); |
|
439 return NS_OK; |
|
440 } |
|
441 |
|
442 /* readonly attribute AUTF8String sourceLine; */ |
|
443 NS_IMETHODIMP JSStackFrame::GetSourceLine(nsACString& aSourceLine) |
|
444 { |
|
445 aSourceLine.Truncate(); |
|
446 return NS_OK; |
|
447 } |
|
448 |
|
449 /* readonly attribute nsIStackFrame caller; */ |
|
450 NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller) |
|
451 { |
|
452 if (!mCallerInitialized) { |
|
453 mCaller = new JSStackFrame(mStackDescription, mIndex+1); |
|
454 mCallerInitialized = true; |
|
455 } |
|
456 NS_IF_ADDREF(*aCaller = mCaller); |
|
457 return NS_OK; |
|
458 } |
|
459 |
|
460 /* AUTF8String toString (); */ |
|
461 NS_IMETHODIMP JSStackFrame::ToString(nsACString& _retval) |
|
462 { |
|
463 _retval.Truncate(); |
|
464 |
|
465 const char* frametype = IsJSFrame() ? "JS" : "native"; |
|
466 |
|
467 nsString filename; |
|
468 nsresult rv = GetFilename(filename); |
|
469 NS_ENSURE_SUCCESS(rv, rv); |
|
470 |
|
471 if (filename.IsEmpty()) { |
|
472 filename.AssignLiteral("<unknown filename>"); |
|
473 } |
|
474 |
|
475 nsString funname; |
|
476 rv = GetName(funname); |
|
477 NS_ENSURE_SUCCESS(rv, rv); |
|
478 |
|
479 if (funname.IsEmpty()) { |
|
480 funname.AssignLiteral("<TOP_LEVEL>"); |
|
481 } |
|
482 static const char format[] = "%s frame :: %s :: %s :: line %d"; |
|
483 _retval.AppendPrintf(format, frametype, |
|
484 NS_ConvertUTF16toUTF8(filename).get(), |
|
485 NS_ConvertUTF16toUTF8(funname).get(), |
|
486 GetLineno()); |
|
487 return NS_OK; |
|
488 } |
|
489 |
|
490 /* static */ already_AddRefed<nsIStackFrame> |
|
491 JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth) |
|
492 { |
|
493 static const unsigned MAX_FRAMES = 100; |
|
494 if (aMaxDepth < 0) { |
|
495 aMaxDepth = MAX_FRAMES; |
|
496 } |
|
497 |
|
498 JS::StackDescription* desc = JS::DescribeStack(aCx, aMaxDepth); |
|
499 if (!desc) { |
|
500 return nullptr; |
|
501 } |
|
502 |
|
503 nsRefPtr<StackDescriptionOwner> descOwner = new StackDescriptionOwner(desc); |
|
504 |
|
505 nsRefPtr<JSStackFrame> first = new JSStackFrame(descOwner, 0); |
|
506 return first.forget(); |
|
507 } |
|
508 |
|
509 /* static */ already_AddRefed<nsIStackFrame> |
|
510 JSStackFrame::CreateStackFrameLocation(uint32_t aLanguage, |
|
511 const char* aFilename, |
|
512 const char* aFunctionName, |
|
513 int32_t aLineNumber, |
|
514 nsIStackFrame* aCaller) |
|
515 { |
|
516 nsRefPtr<JSStackFrame> self = new JSStackFrame(nullptr, 0); |
|
517 |
|
518 self->mLanguage = aLanguage; |
|
519 self->mLineno = aLineNumber; |
|
520 CopyUTF8toUTF16(aFilename, self->mFilename); |
|
521 CopyUTF8toUTF16(aFunctionName, self->mFunname); |
|
522 |
|
523 self->mCaller = aCaller; |
|
524 |
|
525 return self.forget(); |
|
526 } |
|
527 |
|
528 already_AddRefed<nsIStackFrame> |
|
529 CreateStack(JSContext* aCx, int32_t aMaxDepth) |
|
530 { |
|
531 return JSStackFrame::CreateStack(aCx, aMaxDepth); |
|
532 } |
|
533 |
|
534 already_AddRefed<nsIStackFrame> |
|
535 CreateStackFrameLocation(uint32_t aLanguage, |
|
536 const char* aFilename, |
|
537 const char* aFunctionName, |
|
538 int32_t aLineNumber, |
|
539 nsIStackFrame* aCaller) |
|
540 { |
|
541 return JSStackFrame::CreateStackFrameLocation(aLanguage, aFilename, |
|
542 aFunctionName, aLineNumber, |
|
543 aCaller); |
|
544 } |
|
545 |
|
546 } // namespace exceptions |
|
547 } // namespace dom |
|
548 } // namespace mozilla |