dom/bindings/Exceptions.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:5520c229b4a5
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

mercurial