/src/mozilla-central/dom/bindings/Exceptions.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/dom/Exceptions.h" |
8 | | |
9 | | #include "js/RootingAPI.h" |
10 | | #include "js/TypeDecls.h" |
11 | | #include "jsapi.h" |
12 | | #include "js/SavedFrameAPI.h" |
13 | | #include "mozilla/CycleCollectedJSContext.h" |
14 | | #include "mozilla/dom/BindingUtils.h" |
15 | | #include "mozilla/dom/DOMException.h" |
16 | | #include "mozilla/dom/ScriptSettings.h" |
17 | | #include "nsPIDOMWindow.h" |
18 | | #include "nsServiceManagerUtils.h" |
19 | | #include "nsThreadUtils.h" |
20 | | #include "XPCWrapper.h" |
21 | | #include "WorkerPrivate.h" |
22 | | #include "nsContentUtils.h" |
23 | | |
24 | | namespace mozilla { |
25 | | namespace dom { |
26 | | |
27 | | // Throw the given exception value if it's safe. If it's not safe, then |
28 | | // synthesize and throw a new exception value for NS_ERROR_UNEXPECTED. The |
29 | | // incoming value must be in the compartment of aCx. This function guarantees |
30 | | // that an exception is pending on aCx when it returns. |
31 | | static void |
32 | | ThrowExceptionValueIfSafe(JSContext* aCx, JS::Handle<JS::Value> exnVal, |
33 | | Exception* aOriginalException) |
34 | 0 | { |
35 | 0 | MOZ_ASSERT(aOriginalException); |
36 | 0 |
|
37 | 0 | if (!exnVal.isObject()) { |
38 | 0 | JS_SetPendingException(aCx, exnVal); |
39 | 0 | return; |
40 | 0 | } |
41 | 0 | |
42 | 0 | JS::Rooted<JSObject*> exnObj(aCx, &exnVal.toObject()); |
43 | 0 | MOZ_ASSERT(js::IsObjectInContextCompartment(exnObj, aCx), |
44 | 0 | "exnObj needs to be in the right compartment for the " |
45 | 0 | "CheckedUnwrap thing to make sense"); |
46 | 0 |
|
47 | 0 | if (js::CheckedUnwrap(exnObj)) { |
48 | 0 | // This is an object we're allowed to work with, so just go ahead and throw |
49 | 0 | // it. |
50 | 0 | JS_SetPendingException(aCx, exnVal); |
51 | 0 | return; |
52 | 0 | } |
53 | 0 | |
54 | 0 | // We could probably Throw(aCx, NS_ERROR_UNEXPECTED) here, and it would do the |
55 | 0 | // right thing due to there not being an existing exception on the runtime at |
56 | 0 | // this point, but it's clearer to explicitly do the thing we want done. This |
57 | 0 | // is also why we don't just call ThrowExceptionObject on the Exception we |
58 | 0 | // create: it would do the right thing, but that fact is not obvious. |
59 | 0 | RefPtr<Exception> syntheticException = |
60 | 0 | CreateException(NS_ERROR_UNEXPECTED); |
61 | 0 | JS::Rooted<JS::Value> syntheticVal(aCx); |
62 | 0 | if (!GetOrCreateDOMReflector(aCx, syntheticException, &syntheticVal)) { |
63 | 0 | return; |
64 | 0 | } |
65 | 0 | MOZ_ASSERT(syntheticVal.isObject() && |
66 | 0 | !js::IsWrapper(&syntheticVal.toObject()), |
67 | 0 | "Must have a reflector here, not a wrapper"); |
68 | 0 | JS_SetPendingException(aCx, syntheticVal); |
69 | 0 | } |
70 | | |
71 | | void |
72 | | ThrowExceptionObject(JSContext* aCx, Exception* aException) |
73 | 0 | { |
74 | 0 | JS::Rooted<JS::Value> thrown(aCx); |
75 | 0 |
|
76 | 0 | // If we stored the original thrown JS value in the exception |
77 | 0 | // (see XPCConvert::ConstructException) and we are in a web context |
78 | 0 | // (i.e., not chrome), rethrow the original value. This only applies to JS |
79 | 0 | // implemented components so we only need to check for this on the main |
80 | 0 | // thread. |
81 | 0 | if (NS_IsMainThread() && !nsContentUtils::IsCallerChrome() && |
82 | 0 | aException->StealJSVal(thrown.address())) { |
83 | 0 | // Now check for the case when thrown is a number which matches |
84 | 0 | // aException->GetResult(). This would indicate that what actually got |
85 | 0 | // thrown was an nsresult value. In that situation, we should go back |
86 | 0 | // through dom::Throw with that nsresult value, because it will make sure to |
87 | 0 | // create the right sort of Exception or DOMException, with the right |
88 | 0 | // global. |
89 | 0 | if (thrown.isNumber()) { |
90 | 0 | nsresult exceptionResult = aException->GetResult(); |
91 | 0 | if (double(exceptionResult) == thrown.toNumber()) { |
92 | 0 | Throw(aCx, exceptionResult); |
93 | 0 | return; |
94 | 0 | } |
95 | 0 | } |
96 | 0 | if (!JS_WrapValue(aCx, &thrown)) { |
97 | 0 | return; |
98 | 0 | } |
99 | 0 | ThrowExceptionValueIfSafe(aCx, thrown, aException); |
100 | 0 | return; |
101 | 0 | } |
102 | 0 | |
103 | 0 | if (!GetOrCreateDOMReflector(aCx, aException, &thrown)) { |
104 | 0 | return; |
105 | 0 | } |
106 | 0 | |
107 | 0 | ThrowExceptionValueIfSafe(aCx, thrown, aException); |
108 | 0 | } |
109 | | |
110 | | bool |
111 | | Throw(JSContext* aCx, nsresult aRv, const nsACString& aMessage) |
112 | 0 | { |
113 | 0 | if (aRv == NS_ERROR_UNCATCHABLE_EXCEPTION) { |
114 | 0 | // Nuke any existing exception on aCx, to make sure we're uncatchable. |
115 | 0 | JS_ClearPendingException(aCx); |
116 | 0 | return false; |
117 | 0 | } |
118 | 0 | |
119 | 0 | if (JS_IsExceptionPending(aCx)) { |
120 | 0 | // Don't clobber the existing exception. |
121 | 0 | return false; |
122 | 0 | } |
123 | 0 | |
124 | 0 | CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); |
125 | 0 | RefPtr<Exception> existingException = context->GetPendingException(); |
126 | 0 | // Make sure to clear the pending exception now. Either we're going to reuse |
127 | 0 | // it (and we already grabbed it), or we plan to throw something else and this |
128 | 0 | // pending exception is no longer relevant. |
129 | 0 | context->SetPendingException(nullptr); |
130 | 0 |
|
131 | 0 | // Ignore the pending exception if we have a non-default message passed in. |
132 | 0 | if (aMessage.IsEmpty() && existingException) { |
133 | 0 | if (aRv == existingException->GetResult()) { |
134 | 0 | // Reuse the existing exception. |
135 | 0 | ThrowExceptionObject(aCx, existingException); |
136 | 0 | return false; |
137 | 0 | } |
138 | 0 | } |
139 | 0 | |
140 | 0 | RefPtr<Exception> finalException = CreateException(aRv, aMessage); |
141 | 0 | MOZ_ASSERT(finalException); |
142 | 0 |
|
143 | 0 | ThrowExceptionObject(aCx, finalException); |
144 | 0 | return false; |
145 | 0 | } |
146 | | |
147 | | void |
148 | | ThrowAndReport(nsPIDOMWindowInner* aWindow, nsresult aRv) |
149 | 0 | { |
150 | 0 | MOZ_ASSERT(aRv != NS_ERROR_UNCATCHABLE_EXCEPTION, |
151 | 0 | "Doesn't make sense to report uncatchable exceptions!"); |
152 | 0 | AutoJSAPI jsapi; |
153 | 0 | if (NS_WARN_IF(!jsapi.Init(aWindow))) { |
154 | 0 | return; |
155 | 0 | } |
156 | 0 | |
157 | 0 | Throw(jsapi.cx(), aRv); |
158 | 0 | } |
159 | | |
160 | | already_AddRefed<Exception> |
161 | | CreateException(nsresult aRv, const nsACString& aMessage) |
162 | 0 | { |
163 | 0 | // Do we use DOM exceptions for this error code? |
164 | 0 | switch (NS_ERROR_GET_MODULE(aRv)) { |
165 | 0 | case NS_ERROR_MODULE_DOM: |
166 | 0 | case NS_ERROR_MODULE_SVG: |
167 | 0 | case NS_ERROR_MODULE_DOM_XPATH: |
168 | 0 | case NS_ERROR_MODULE_DOM_INDEXEDDB: |
169 | 0 | case NS_ERROR_MODULE_DOM_FILEHANDLE: |
170 | 0 | case NS_ERROR_MODULE_DOM_ANIM: |
171 | 0 | case NS_ERROR_MODULE_DOM_PUSH: |
172 | 0 | case NS_ERROR_MODULE_DOM_MEDIA: |
173 | 0 | if (aMessage.IsEmpty()) { |
174 | 0 | return DOMException::Create(aRv); |
175 | 0 | } |
176 | 0 | return DOMException::Create(aRv, aMessage); |
177 | 0 | default: |
178 | 0 | break; |
179 | 0 | } |
180 | 0 | |
181 | 0 | // If not, use the default. |
182 | 0 | RefPtr<Exception> exception = |
183 | 0 | new Exception(aMessage, aRv, EmptyCString(), nullptr, nullptr); |
184 | 0 | return exception.forget(); |
185 | 0 | } |
186 | | |
187 | | already_AddRefed<nsIStackFrame> |
188 | | GetCurrentJSStack(int32_t aMaxDepth) |
189 | 0 | { |
190 | 0 | // is there a current context available? |
191 | 0 | JSContext* cx = nsContentUtils::GetCurrentJSContext(); |
192 | 0 |
|
193 | 0 | if (!cx || !js::GetContextRealm(cx)) { |
194 | 0 | return nullptr; |
195 | 0 | } |
196 | 0 | |
197 | 0 | static const unsigned MAX_FRAMES = 100; |
198 | 0 | if (aMaxDepth < 0) { |
199 | 0 | aMaxDepth = MAX_FRAMES; |
200 | 0 | } |
201 | 0 |
|
202 | 0 | JS::StackCapture captureMode = aMaxDepth == 0 |
203 | 0 | ? JS::StackCapture(JS::AllFrames()) |
204 | 0 | : JS::StackCapture(JS::MaxFrames(aMaxDepth)); |
205 | 0 |
|
206 | 0 | return dom::exceptions::CreateStack(cx, std::move(captureMode)); |
207 | 0 | } |
208 | | |
209 | | namespace exceptions { |
210 | | |
211 | | class JSStackFrame : public nsIStackFrame |
212 | | { |
213 | | public: |
214 | | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
215 | | NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSStackFrame) |
216 | | NS_DECL_NSISTACKFRAME |
217 | | |
218 | | // aStack must not be null. |
219 | | explicit JSStackFrame(JS::Handle<JSObject*> aStack); |
220 | | |
221 | | private: |
222 | | virtual ~JSStackFrame(); |
223 | | |
224 | | JS::Heap<JSObject*> mStack; |
225 | | nsString mFormattedStack; |
226 | | |
227 | | nsCOMPtr<nsIStackFrame> mCaller; |
228 | | nsCOMPtr<nsIStackFrame> mAsyncCaller; |
229 | | nsString mFilename; |
230 | | nsString mFunname; |
231 | | nsString mAsyncCause; |
232 | | int32_t mLineno; |
233 | | int32_t mColNo; |
234 | | |
235 | | bool mFilenameInitialized; |
236 | | bool mFunnameInitialized; |
237 | | bool mLinenoInitialized; |
238 | | bool mColNoInitialized; |
239 | | bool mAsyncCauseInitialized; |
240 | | bool mAsyncCallerInitialized; |
241 | | bool mCallerInitialized; |
242 | | bool mFormattedStackInitialized; |
243 | | }; |
244 | | |
245 | | JSStackFrame::JSStackFrame(JS::Handle<JSObject*> aStack) |
246 | | : mStack(aStack) |
247 | | , mLineno(0) |
248 | | , mColNo(0) |
249 | | , mFilenameInitialized(false) |
250 | | , mFunnameInitialized(false) |
251 | | , mLinenoInitialized(false) |
252 | | , mColNoInitialized(false) |
253 | | , mAsyncCauseInitialized(false) |
254 | | , mAsyncCallerInitialized(false) |
255 | | , mCallerInitialized(false) |
256 | | , mFormattedStackInitialized(false) |
257 | 0 | { |
258 | 0 | MOZ_ASSERT(mStack); |
259 | 0 | MOZ_ASSERT(JS::IsUnwrappedSavedFrame(mStack)); |
260 | 0 |
|
261 | 0 | mozilla::HoldJSObjects(this); |
262 | 0 | } |
263 | | |
264 | | JSStackFrame::~JSStackFrame() |
265 | 0 | { |
266 | 0 | mozilla::DropJSObjects(this); |
267 | 0 | } |
268 | | |
269 | | NS_IMPL_CYCLE_COLLECTION_CLASS(JSStackFrame) |
270 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSStackFrame) |
271 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCaller) |
272 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mAsyncCaller) |
273 | 0 | tmp->mStack = nullptr; |
274 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
275 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSStackFrame) |
276 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCaller) |
277 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAsyncCaller) |
278 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
279 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSStackFrame) |
280 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStack) |
281 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
282 | | |
283 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(JSStackFrame) |
284 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(JSStackFrame) |
285 | | |
286 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSStackFrame) |
287 | 0 | NS_INTERFACE_MAP_ENTRY(nsIStackFrame) |
288 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
289 | 0 | NS_INTERFACE_MAP_END |
290 | | |
291 | | // Helper method to determine the JSPrincipals* to pass to JS SavedFrame APIs. |
292 | | // |
293 | | // @argument aStack the stack we're working with; must be non-null. |
294 | | // @argument [out] aCanCache whether we can use cached JSStackFrame values. |
295 | | static JSPrincipals* |
296 | | GetPrincipalsForStackGetter(JSContext* aCx, JS::Handle<JSObject*> aStack, |
297 | | bool* aCanCache) |
298 | 0 | { |
299 | 0 | MOZ_ASSERT(JS::IsUnwrappedSavedFrame(aStack)); |
300 | 0 |
|
301 | 0 | JSPrincipals* currentPrincipals = |
302 | 0 | JS::GetRealmPrincipals(js::GetContextRealm(aCx)); |
303 | 0 | JSPrincipals* stackPrincipals = |
304 | 0 | JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(aStack)); |
305 | 0 |
|
306 | 0 | // Fast path for when the principals are equal. This check is also necessary |
307 | 0 | // for workers: no nsIPrincipal there so we can't use the code below. |
308 | 0 | if (currentPrincipals == stackPrincipals) { |
309 | 0 | *aCanCache = true; |
310 | 0 | return stackPrincipals; |
311 | 0 | } |
312 | 0 | |
313 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
314 | 0 |
|
315 | 0 | if (nsJSPrincipals::get(currentPrincipals)->Subsumes( |
316 | 0 | nsJSPrincipals::get(stackPrincipals))) { |
317 | 0 | // The current principals subsume the stack's principals. In this case use |
318 | 0 | // the stack's principals: the idea is that this way devtools code that's |
319 | 0 | // asking an exception object for a stack to display will end up with the |
320 | 0 | // stack the web developer would see via doing .stack in a web page, with |
321 | 0 | // Firefox implementation details excluded. |
322 | 0 |
|
323 | 0 | // Because we use the stack's principals and don't rely on the current |
324 | 0 | // context realm, we can use cached values. |
325 | 0 | *aCanCache = true; |
326 | 0 | return stackPrincipals; |
327 | 0 | } |
328 | 0 | |
329 | 0 | // The stack was captured in more-privileged code, so use the less privileged |
330 | 0 | // principals. Don't use cached values because we don't want these values to |
331 | 0 | // depend on the current realm/principals. |
332 | 0 | *aCanCache = false; |
333 | 0 | return currentPrincipals; |
334 | 0 | } |
335 | | |
336 | | // Helper method to get the value of a stack property, if it's not already |
337 | | // cached. This will make sure we skip the cache if the property value depends |
338 | | // on the (current) context's realm/principals. |
339 | | // |
340 | | // @argument aStack the stack we're working with; must be non-null. |
341 | | // @argument aPropGetter the getter function to call. |
342 | | // @argument aIsCached whether we've cached this property's value before. |
343 | | // |
344 | | // @argument [out] aCanCache whether the value can get cached. |
345 | | // @argument [out] aUseCachedValue if true, just use the cached value. |
346 | | // @argument [out] aValue the value we got from the stack. |
347 | | template<typename ReturnType, typename GetterOutParamType> |
348 | | static void |
349 | | GetValueIfNotCached(JSContext* aCx, const JS::Heap<JSObject*>& aStack, |
350 | | JS::SavedFrameResult (*aPropGetter)(JSContext*, |
351 | | JSPrincipals*, |
352 | | JS::Handle<JSObject*>, |
353 | | GetterOutParamType, |
354 | | JS::SavedFrameSelfHosted), |
355 | | bool aIsCached, bool* aCanCache, bool* aUseCachedValue, |
356 | | ReturnType aValue) |
357 | 0 | { |
358 | 0 | MOZ_ASSERT(aStack); |
359 | 0 | MOZ_ASSERT(JS::IsUnwrappedSavedFrame(aStack)); |
360 | 0 |
|
361 | 0 | JS::Rooted<JSObject*> stack(aCx, aStack); |
362 | 0 |
|
363 | 0 | JSPrincipals* principals = GetPrincipalsForStackGetter(aCx, stack, aCanCache); |
364 | 0 | if (*aCanCache && aIsCached) { |
365 | 0 | *aUseCachedValue = true; |
366 | 0 | return; |
367 | 0 | } |
368 | 0 | |
369 | 0 | *aUseCachedValue = false; |
370 | 0 |
|
371 | 0 | aPropGetter(aCx, principals, stack, aValue, |
372 | 0 | JS::SavedFrameSelfHosted::Exclude); |
373 | 0 | } Unexecuted instantiation: Unified_cpp_dom_bindings0.cpp:void mozilla::dom::exceptions::GetValueIfNotCached<JS::Rooted<JSString*>*, JS::MutableHandle<JSString*> >(JSContext*, JS::Heap<JSObject*> const&, JS::SavedFrameResult (*)(JSContext*, JSPrincipals*, JS::Handle<JSObject*>, JS::MutableHandle<JSString*>, JS::SavedFrameSelfHosted), bool, bool*, bool*, JS::Rooted<JSString*>*) Unexecuted instantiation: Unified_cpp_dom_bindings0.cpp:void mozilla::dom::exceptions::GetValueIfNotCached<unsigned int*, unsigned int*>(JSContext*, JS::Heap<JSObject*> const&, JS::SavedFrameResult (*)(JSContext*, JSPrincipals*, JS::Handle<JSObject*>, unsigned int*, JS::SavedFrameSelfHosted), bool, bool*, bool*, unsigned int*) Unexecuted instantiation: Unified_cpp_dom_bindings0.cpp:void mozilla::dom::exceptions::GetValueIfNotCached<JS::Rooted<JSObject*>*, JS::MutableHandle<JSObject*> >(JSContext*, JS::Heap<JSObject*> const&, JS::SavedFrameResult (*)(JSContext*, JSPrincipals*, JS::Handle<JSObject*>, JS::MutableHandle<JSObject*>, JS::SavedFrameSelfHosted), bool, bool*, bool*, JS::Rooted<JSObject*>*) |
374 | | |
375 | | NS_IMETHODIMP JSStackFrame::GetFilenameXPCOM(JSContext* aCx, nsAString& aFilename) |
376 | 0 | { |
377 | 0 | GetFilename(aCx, aFilename); |
378 | 0 | return NS_OK; |
379 | 0 | } |
380 | | |
381 | | void |
382 | | JSStackFrame::GetFilename(JSContext* aCx, nsAString& aFilename) |
383 | 0 | { |
384 | 0 | if (!mStack) { |
385 | 0 | aFilename.Truncate(); |
386 | 0 | return; |
387 | 0 | } |
388 | 0 | |
389 | 0 | JS::Rooted<JSString*> filename(aCx); |
390 | 0 | bool canCache = false, useCachedValue = false; |
391 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameSource, |
392 | 0 | mFilenameInitialized, |
393 | 0 | &canCache, &useCachedValue, &filename); |
394 | 0 | if (useCachedValue) { |
395 | 0 | aFilename = mFilename; |
396 | 0 | return; |
397 | 0 | } |
398 | 0 | |
399 | 0 | nsAutoJSString str; |
400 | 0 | if (!str.init(aCx, filename)) { |
401 | 0 | JS_ClearPendingException(aCx); |
402 | 0 | aFilename.Truncate(); |
403 | 0 | return; |
404 | 0 | } |
405 | 0 | aFilename = str; |
406 | 0 |
|
407 | 0 | if (canCache) { |
408 | 0 | mFilename = str; |
409 | 0 | mFilenameInitialized = true; |
410 | 0 | } |
411 | 0 | } |
412 | | |
413 | | NS_IMETHODIMP |
414 | | JSStackFrame::GetNameXPCOM(JSContext* aCx, nsAString& aFunction) |
415 | 0 | { |
416 | 0 | GetName(aCx, aFunction); |
417 | 0 | return NS_OK; |
418 | 0 | } |
419 | | |
420 | | void |
421 | | JSStackFrame::GetName(JSContext* aCx, nsAString& aFunction) |
422 | 0 | { |
423 | 0 | if (!mStack) { |
424 | 0 | aFunction.Truncate(); |
425 | 0 | return; |
426 | 0 | } |
427 | 0 | |
428 | 0 | JS::Rooted<JSString*> name(aCx); |
429 | 0 | bool canCache = false, useCachedValue = false; |
430 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameFunctionDisplayName, |
431 | 0 | mFunnameInitialized, &canCache, &useCachedValue, |
432 | 0 | &name); |
433 | 0 |
|
434 | 0 | if (useCachedValue) { |
435 | 0 | aFunction = mFunname; |
436 | 0 | return; |
437 | 0 | } |
438 | 0 | |
439 | 0 | if (name) { |
440 | 0 | nsAutoJSString str; |
441 | 0 | if (!str.init(aCx, name)) { |
442 | 0 | JS_ClearPendingException(aCx); |
443 | 0 | aFunction.Truncate(); |
444 | 0 | return; |
445 | 0 | } |
446 | 0 | aFunction = str; |
447 | 0 | } else { |
448 | 0 | aFunction.SetIsVoid(true); |
449 | 0 | } |
450 | 0 |
|
451 | 0 | if (canCache) { |
452 | 0 | mFunname = aFunction; |
453 | 0 | mFunnameInitialized = true; |
454 | 0 | } |
455 | 0 | } |
456 | | |
457 | | int32_t |
458 | | JSStackFrame::GetLineNumber(JSContext* aCx) |
459 | 0 | { |
460 | 0 | if (!mStack) { |
461 | 0 | return 0; |
462 | 0 | } |
463 | 0 | |
464 | 0 | uint32_t line; |
465 | 0 | bool canCache = false, useCachedValue = false; |
466 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameLine, mLinenoInitialized, |
467 | 0 | &canCache, &useCachedValue, &line); |
468 | 0 |
|
469 | 0 | if (useCachedValue) { |
470 | 0 | return mLineno; |
471 | 0 | } |
472 | 0 | |
473 | 0 | if (canCache) { |
474 | 0 | mLineno = line; |
475 | 0 | mLinenoInitialized = true; |
476 | 0 | } |
477 | 0 |
|
478 | 0 | return line; |
479 | 0 | } |
480 | | |
481 | | NS_IMETHODIMP |
482 | | JSStackFrame::GetLineNumberXPCOM(JSContext* aCx, int32_t* aLineNumber) |
483 | 0 | { |
484 | 0 | *aLineNumber = GetLineNumber(aCx); |
485 | 0 | return NS_OK; |
486 | 0 | } |
487 | | |
488 | | int32_t |
489 | | JSStackFrame::GetColumnNumber(JSContext* aCx) |
490 | 0 | { |
491 | 0 | if (!mStack) { |
492 | 0 | return 0; |
493 | 0 | } |
494 | 0 | |
495 | 0 | uint32_t col; |
496 | 0 | bool canCache = false, useCachedValue = false; |
497 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameColumn, mColNoInitialized, |
498 | 0 | &canCache, &useCachedValue, &col); |
499 | 0 |
|
500 | 0 | if (useCachedValue) { |
501 | 0 | return mColNo; |
502 | 0 | } |
503 | 0 | |
504 | 0 | if (canCache) { |
505 | 0 | mColNo = col; |
506 | 0 | mColNoInitialized = true; |
507 | 0 | } |
508 | 0 |
|
509 | 0 | return col; |
510 | 0 | } |
511 | | |
512 | | NS_IMETHODIMP |
513 | | JSStackFrame::GetColumnNumberXPCOM(JSContext* aCx, |
514 | | int32_t* aColumnNumber) |
515 | 0 | { |
516 | 0 | *aColumnNumber = GetColumnNumber(aCx); |
517 | 0 | return NS_OK; |
518 | 0 | } |
519 | | |
520 | | NS_IMETHODIMP JSStackFrame::GetSourceLine(nsACString& aSourceLine) |
521 | 0 | { |
522 | 0 | aSourceLine.Truncate(); |
523 | 0 | return NS_OK; |
524 | 0 | } |
525 | | |
526 | | NS_IMETHODIMP |
527 | | JSStackFrame::GetAsyncCauseXPCOM(JSContext* aCx, |
528 | | nsAString& aAsyncCause) |
529 | 0 | { |
530 | 0 | GetAsyncCause(aCx, aAsyncCause); |
531 | 0 | return NS_OK; |
532 | 0 | } |
533 | | |
534 | | void |
535 | | JSStackFrame::GetAsyncCause(JSContext* aCx, |
536 | | nsAString& aAsyncCause) |
537 | 0 | { |
538 | 0 | if (!mStack) { |
539 | 0 | aAsyncCause.Truncate(); |
540 | 0 | return; |
541 | 0 | } |
542 | 0 | |
543 | 0 | JS::Rooted<JSString*> asyncCause(aCx); |
544 | 0 | bool canCache = false, useCachedValue = false; |
545 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameAsyncCause, |
546 | 0 | mAsyncCauseInitialized, &canCache, &useCachedValue, |
547 | 0 | &asyncCause); |
548 | 0 |
|
549 | 0 | if (useCachedValue) { |
550 | 0 | aAsyncCause = mAsyncCause; |
551 | 0 | return; |
552 | 0 | } |
553 | 0 | |
554 | 0 | if (asyncCause) { |
555 | 0 | nsAutoJSString str; |
556 | 0 | if (!str.init(aCx, asyncCause)) { |
557 | 0 | JS_ClearPendingException(aCx); |
558 | 0 | aAsyncCause.Truncate(); |
559 | 0 | return; |
560 | 0 | } |
561 | 0 | aAsyncCause = str; |
562 | 0 | } else { |
563 | 0 | aAsyncCause.SetIsVoid(true); |
564 | 0 | } |
565 | 0 |
|
566 | 0 | if (canCache) { |
567 | 0 | mAsyncCause = aAsyncCause; |
568 | 0 | mAsyncCauseInitialized = true; |
569 | 0 | } |
570 | 0 | } |
571 | | |
572 | | NS_IMETHODIMP |
573 | | JSStackFrame::GetAsyncCallerXPCOM(JSContext* aCx, |
574 | | nsIStackFrame** aAsyncCaller) |
575 | 0 | { |
576 | 0 | *aAsyncCaller = GetAsyncCaller(aCx).take(); |
577 | 0 | return NS_OK; |
578 | 0 | } |
579 | | |
580 | | already_AddRefed<nsIStackFrame> |
581 | | JSStackFrame::GetAsyncCaller(JSContext* aCx) |
582 | 0 | { |
583 | 0 | if (!mStack) { |
584 | 0 | return nullptr; |
585 | 0 | } |
586 | 0 | |
587 | 0 | JS::Rooted<JSObject*> asyncCallerObj(aCx); |
588 | 0 | bool canCache = false, useCachedValue = false; |
589 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameAsyncParent, |
590 | 0 | mAsyncCallerInitialized, &canCache, &useCachedValue, |
591 | 0 | &asyncCallerObj); |
592 | 0 |
|
593 | 0 | if (useCachedValue) { |
594 | 0 | nsCOMPtr<nsIStackFrame> asyncCaller = mAsyncCaller; |
595 | 0 | return asyncCaller.forget(); |
596 | 0 | } |
597 | 0 | |
598 | 0 | nsCOMPtr<nsIStackFrame> asyncCaller = |
599 | 0 | asyncCallerObj ? new JSStackFrame(asyncCallerObj) : nullptr; |
600 | 0 |
|
601 | 0 | if (canCache) { |
602 | 0 | mAsyncCaller = asyncCaller; |
603 | 0 | mAsyncCallerInitialized = true; |
604 | 0 | } |
605 | 0 |
|
606 | 0 | return asyncCaller.forget(); |
607 | 0 | } |
608 | | |
609 | | NS_IMETHODIMP |
610 | | JSStackFrame::GetCallerXPCOM(JSContext* aCx, nsIStackFrame** aCaller) |
611 | 0 | { |
612 | 0 | *aCaller = GetCaller(aCx).take(); |
613 | 0 | return NS_OK; |
614 | 0 | } |
615 | | |
616 | | already_AddRefed<nsIStackFrame> |
617 | | JSStackFrame::GetCaller(JSContext* aCx) |
618 | 0 | { |
619 | 0 | if (!mStack) { |
620 | 0 | return nullptr; |
621 | 0 | } |
622 | 0 | |
623 | 0 | JS::Rooted<JSObject*> callerObj(aCx); |
624 | 0 | bool canCache = false, useCachedValue = false; |
625 | 0 | GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameParent, mCallerInitialized, |
626 | 0 | &canCache, &useCachedValue, &callerObj); |
627 | 0 |
|
628 | 0 | if (useCachedValue) { |
629 | 0 | nsCOMPtr<nsIStackFrame> caller = mCaller; |
630 | 0 | return caller.forget(); |
631 | 0 | } |
632 | 0 | |
633 | 0 | nsCOMPtr<nsIStackFrame> caller = |
634 | 0 | callerObj ? new JSStackFrame(callerObj) : nullptr; |
635 | 0 |
|
636 | 0 | if (canCache) { |
637 | 0 | mCaller = caller; |
638 | 0 | mCallerInitialized = true; |
639 | 0 | } |
640 | 0 |
|
641 | 0 | return caller.forget(); |
642 | 0 | } |
643 | | |
644 | | NS_IMETHODIMP |
645 | | JSStackFrame::GetFormattedStackXPCOM(JSContext* aCx, nsAString& aStack) |
646 | 0 | { |
647 | 0 | GetFormattedStack(aCx, aStack); |
648 | 0 | return NS_OK; |
649 | 0 | } |
650 | | |
651 | | void |
652 | | JSStackFrame::GetFormattedStack(JSContext* aCx, nsAString& aStack) |
653 | 0 | { |
654 | 0 | if (!mStack) { |
655 | 0 | aStack.Truncate(); |
656 | 0 | return; |
657 | 0 | } |
658 | 0 | |
659 | 0 | // Sadly we can't use GetValueIfNotCached here, because our getter |
660 | 0 | // returns bool, not JS::SavedFrameResult. Maybe it's possible to |
661 | 0 | // make the templates more complicated to deal, but in the meantime |
662 | 0 | // let's just inline GetValueIfNotCached here. |
663 | 0 | |
664 | 0 | JS::Rooted<JSObject*> stack(aCx, mStack); |
665 | 0 |
|
666 | 0 | bool canCache; |
667 | 0 | JSPrincipals* principals = GetPrincipalsForStackGetter(aCx, stack, &canCache); |
668 | 0 | if (canCache && mFormattedStackInitialized) { |
669 | 0 | aStack = mFormattedStack; |
670 | 0 | return; |
671 | 0 | } |
672 | 0 | |
673 | 0 | JS::Rooted<JSString*> formattedStack(aCx); |
674 | 0 | if (!JS::BuildStackString(aCx, principals, stack, &formattedStack)) { |
675 | 0 | JS_ClearPendingException(aCx); |
676 | 0 | aStack.Truncate(); |
677 | 0 | return; |
678 | 0 | } |
679 | 0 | |
680 | 0 | nsAutoJSString str; |
681 | 0 | if (!str.init(aCx, formattedStack)) { |
682 | 0 | JS_ClearPendingException(aCx); |
683 | 0 | aStack.Truncate(); |
684 | 0 | return; |
685 | 0 | } |
686 | 0 | |
687 | 0 | aStack = str; |
688 | 0 |
|
689 | 0 | if (canCache) { |
690 | 0 | mFormattedStack = str; |
691 | 0 | mFormattedStackInitialized = true; |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | | NS_IMETHODIMP JSStackFrame::GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame) |
696 | 0 | { |
697 | 0 | aSavedFrame.setObjectOrNull(mStack); |
698 | 0 | return NS_OK; |
699 | 0 | } |
700 | | |
701 | | NS_IMETHODIMP |
702 | | JSStackFrame::ToStringXPCOM(JSContext* aCx, nsACString& _retval) |
703 | 0 | { |
704 | 0 | ToString(aCx, _retval); |
705 | 0 | return NS_OK; |
706 | 0 | } |
707 | | |
708 | | void |
709 | | JSStackFrame::ToString(JSContext* aCx, nsACString& _retval) |
710 | 0 | { |
711 | 0 | _retval.Truncate(); |
712 | 0 |
|
713 | 0 | nsString filename; |
714 | 0 | GetFilename(aCx, filename); |
715 | 0 |
|
716 | 0 | if (filename.IsEmpty()) { |
717 | 0 | filename.AssignLiteral("<unknown filename>"); |
718 | 0 | } |
719 | 0 |
|
720 | 0 | nsString funname; |
721 | 0 | GetName(aCx, funname); |
722 | 0 |
|
723 | 0 | if (funname.IsEmpty()) { |
724 | 0 | funname.AssignLiteral("<TOP_LEVEL>"); |
725 | 0 | } |
726 | 0 |
|
727 | 0 | int32_t lineno = GetLineNumber(aCx); |
728 | 0 |
|
729 | 0 | static const char format[] = "JS frame :: %s :: %s :: line %d"; |
730 | 0 | _retval.AppendPrintf(format, |
731 | 0 | NS_ConvertUTF16toUTF8(filename).get(), |
732 | 0 | NS_ConvertUTF16toUTF8(funname).get(), |
733 | 0 | lineno); |
734 | 0 | } |
735 | | |
736 | | already_AddRefed<nsIStackFrame> |
737 | | CreateStack(JSContext* aCx, JS::StackCapture&& aCaptureMode) |
738 | 0 | { |
739 | 0 | JS::Rooted<JSObject*> stack(aCx); |
740 | 0 | if (!JS::CaptureCurrentStack(aCx, &stack, std::move(aCaptureMode))) { |
741 | 0 | return nullptr; |
742 | 0 | } |
743 | 0 | |
744 | 0 | if (!stack) { |
745 | 0 | return nullptr; |
746 | 0 | } |
747 | 0 | |
748 | 0 | nsCOMPtr<nsIStackFrame> frame = new JSStackFrame(stack); |
749 | 0 | return frame.forget(); |
750 | 0 | } |
751 | | |
752 | | } // namespace exceptions |
753 | | } // namespace dom |
754 | | } // namespace mozilla |