Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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 : /*
8 : * JS standard exception implementation.
9 : */
10 :
11 : #include "jsexn.h"
12 :
13 : #include "mozilla/ArrayUtils.h"
14 : #include "mozilla/PodOperations.h"
15 : #include "mozilla/Sprintf.h"
16 :
17 : #include <string.h>
18 :
19 : #include "jsapi.h"
20 : #include "jscntxt.h"
21 : #include "jsfun.h"
22 : #include "jsnum.h"
23 : #include "jsobj.h"
24 : #include "jsprf.h"
25 : #include "jsscript.h"
26 : #include "jstypes.h"
27 : #include "jsutil.h"
28 : #include "jswrapper.h"
29 :
30 : #include "gc/Marking.h"
31 : #include "js/CharacterEncoding.h"
32 : #include "vm/ErrorObject.h"
33 : #include "vm/GlobalObject.h"
34 : #include "vm/SavedStacks.h"
35 : #include "vm/SelfHosting.h"
36 : #include "vm/StringBuffer.h"
37 :
38 : #include "jsobjinlines.h"
39 :
40 : #include "vm/ErrorObject-inl.h"
41 : #include "vm/SavedStacks-inl.h"
42 :
43 : using namespace js;
44 : using namespace js::gc;
45 :
46 : using mozilla::ArrayLength;
47 : using mozilla::PodArrayZero;
48 :
49 : static void
50 : exn_finalize(FreeOp* fop, JSObject* obj);
51 :
52 : static bool
53 : exn_toSource(JSContext* cx, unsigned argc, Value* vp);
54 :
55 : #define IMPLEMENT_ERROR_PROTO_CLASS(name) \
56 : { \
57 : js_Object_str, \
58 : JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
59 : JS_NULL_CLASS_OPS, \
60 : &ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
61 : }
62 :
63 : const Class
64 : ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
65 : IMPLEMENT_ERROR_PROTO_CLASS(Error),
66 :
67 : IMPLEMENT_ERROR_PROTO_CLASS(InternalError),
68 : IMPLEMENT_ERROR_PROTO_CLASS(EvalError),
69 : IMPLEMENT_ERROR_PROTO_CLASS(RangeError),
70 : IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError),
71 : IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError),
72 : IMPLEMENT_ERROR_PROTO_CLASS(TypeError),
73 : IMPLEMENT_ERROR_PROTO_CLASS(URIError),
74 :
75 : IMPLEMENT_ERROR_PROTO_CLASS(DebuggeeWouldRun),
76 : IMPLEMENT_ERROR_PROTO_CLASS(CompileError),
77 : IMPLEMENT_ERROR_PROTO_CLASS(LinkError),
78 : IMPLEMENT_ERROR_PROTO_CLASS(RuntimeError)
79 : };
80 :
81 : static const JSFunctionSpec error_methods[] = {
82 : #if JS_HAS_TOSOURCE
83 : JS_FN(js_toSource_str, exn_toSource, 0, 0),
84 : #endif
85 : JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0),
86 : JS_FS_END
87 : };
88 :
89 : static const JSPropertySpec error_properties[] = {
90 : JS_STRING_PS("message", "", 0),
91 : JS_STRING_PS("name", "Error", 0),
92 : // Only Error.prototype has .stack!
93 : JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0),
94 : JS_PS_END
95 : };
96 :
97 : #define IMPLEMENT_ERROR_PROPERTIES(name) \
98 : { \
99 : JS_STRING_PS("message", "", 0), \
100 : JS_STRING_PS("name", #name, 0), \
101 : JS_PS_END \
102 : }
103 :
104 : static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = {
105 : IMPLEMENT_ERROR_PROPERTIES(InternalError),
106 : IMPLEMENT_ERROR_PROPERTIES(EvalError),
107 : IMPLEMENT_ERROR_PROPERTIES(RangeError),
108 : IMPLEMENT_ERROR_PROPERTIES(ReferenceError),
109 : IMPLEMENT_ERROR_PROPERTIES(SyntaxError),
110 : IMPLEMENT_ERROR_PROPERTIES(TypeError),
111 : IMPLEMENT_ERROR_PROPERTIES(URIError),
112 : IMPLEMENT_ERROR_PROPERTIES(DebuggeeWouldRun),
113 : IMPLEMENT_ERROR_PROPERTIES(CompileError),
114 : IMPLEMENT_ERROR_PROPERTIES(LinkError),
115 : IMPLEMENT_ERROR_PROPERTIES(RuntimeError)
116 : };
117 :
118 : #define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
119 : { \
120 : ErrorObject::createConstructor, \
121 : ErrorObject::createProto, \
122 : nullptr, \
123 : nullptr, \
124 : nullptr, \
125 : other_error_properties[JSProto_##name - JSProto_Error - 1], \
126 : nullptr, \
127 : JSProto_Error \
128 : }
129 :
130 : #define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
131 : { \
132 : ErrorObject::createConstructor, \
133 : ErrorObject::createProto, \
134 : nullptr, \
135 : nullptr, \
136 : nullptr, \
137 : other_error_properties[JSProto_##name - JSProto_Error - 1], \
138 : nullptr, \
139 : JSProto_Error | ClassSpec::DontDefineConstructor \
140 : }
141 :
142 : const ClassSpec
143 : ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
144 : {
145 : ErrorObject::createConstructor,
146 : ErrorObject::createProto,
147 : nullptr,
148 : nullptr,
149 : error_methods,
150 : error_properties
151 : },
152 :
153 : IMPLEMENT_NATIVE_ERROR_SPEC(InternalError),
154 : IMPLEMENT_NATIVE_ERROR_SPEC(EvalError),
155 : IMPLEMENT_NATIVE_ERROR_SPEC(RangeError),
156 : IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError),
157 : IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError),
158 : IMPLEMENT_NATIVE_ERROR_SPEC(TypeError),
159 : IMPLEMENT_NATIVE_ERROR_SPEC(URIError),
160 :
161 : IMPLEMENT_NONGLOBAL_ERROR_SPEC(DebuggeeWouldRun),
162 : IMPLEMENT_NONGLOBAL_ERROR_SPEC(CompileError),
163 : IMPLEMENT_NONGLOBAL_ERROR_SPEC(LinkError),
164 : IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError)
165 : };
166 :
167 : #define IMPLEMENT_ERROR_CLASS(name) \
168 : { \
169 : js_Error_str, /* yes, really */ \
170 : JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
171 : JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \
172 : JSCLASS_BACKGROUND_FINALIZE, \
173 : &ErrorObjectClassOps, \
174 : &ErrorObject::classSpecs[JSProto_##name - JSProto_Error ] \
175 : }
176 :
177 : static const ClassOps ErrorObjectClassOps = {
178 : nullptr, /* addProperty */
179 : nullptr, /* delProperty */
180 : nullptr, /* getProperty */
181 : nullptr, /* setProperty */
182 : nullptr, /* enumerate */
183 : nullptr, /* newEnumerate */
184 : nullptr, /* resolve */
185 : nullptr, /* mayResolve */
186 : exn_finalize,
187 : nullptr, /* call */
188 : nullptr, /* hasInstance */
189 : nullptr, /* construct */
190 : nullptr, /* trace */
191 : };
192 :
193 : const Class
194 : ErrorObject::classes[JSEXN_ERROR_LIMIT] = {
195 : IMPLEMENT_ERROR_CLASS(Error),
196 : IMPLEMENT_ERROR_CLASS(InternalError),
197 : IMPLEMENT_ERROR_CLASS(EvalError),
198 : IMPLEMENT_ERROR_CLASS(RangeError),
199 : IMPLEMENT_ERROR_CLASS(ReferenceError),
200 : IMPLEMENT_ERROR_CLASS(SyntaxError),
201 : IMPLEMENT_ERROR_CLASS(TypeError),
202 : IMPLEMENT_ERROR_CLASS(URIError),
203 : // These Error subclasses are not accessible via the global object:
204 : IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun),
205 : IMPLEMENT_ERROR_CLASS(CompileError),
206 : IMPLEMENT_ERROR_CLASS(LinkError),
207 : IMPLEMENT_ERROR_CLASS(RuntimeError)
208 : };
209 :
210 : size_t
211 2 : ExtraMallocSize(JSErrorReport* report)
212 : {
213 2 : if (report->linebuf()) {
214 : /*
215 : * Count with null terminator and alignment.
216 : * See CopyExtraData for the details about alignment.
217 : */
218 0 : return (report->linebufLength() + 1) * sizeof(char16_t) + 1;
219 : }
220 :
221 2 : return 0;
222 : }
223 :
224 : size_t
225 0 : ExtraMallocSize(JSErrorNotes::Note* note)
226 : {
227 0 : return 0;
228 : }
229 :
230 : bool
231 2 : CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorReport* copy, JSErrorReport* report)
232 : {
233 2 : if (report->linebuf()) {
234 : /*
235 : * Make sure cursor is properly aligned for char16_t for platforms
236 : * which need it and it's at the end of the buffer on exit.
237 : */
238 0 : size_t alignment_backlog = 0;
239 0 : if (size_t(*cursor) % 2)
240 0 : (*cursor)++;
241 : else
242 0 : alignment_backlog = 1;
243 :
244 0 : size_t linebufSize = (report->linebufLength() + 1) * sizeof(char16_t);
245 0 : const char16_t* linebufCopy = (const char16_t*)(*cursor);
246 0 : js_memcpy(*cursor, report->linebuf(), linebufSize);
247 0 : *cursor += linebufSize + alignment_backlog;
248 0 : copy->initBorrowedLinebuf(linebufCopy, report->linebufLength(), report->tokenOffset());
249 : }
250 :
251 : /* Copy non-pointer members. */
252 2 : copy->isMuted = report->isMuted;
253 2 : copy->exnType = report->exnType;
254 :
255 : /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
256 2 : copy->flags = report->flags;
257 :
258 : /* Deep copy notes. */
259 2 : if (report->notes) {
260 0 : auto copiedNotes = report->notes->copy(cx);
261 0 : if (!copiedNotes)
262 0 : return false;
263 0 : copy->notes = Move(copiedNotes);
264 : } else {
265 2 : copy->notes.reset(nullptr);
266 : }
267 :
268 2 : return true;
269 : }
270 :
271 : bool
272 0 : CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorNotes::Note* copy, JSErrorNotes::Note* report)
273 : {
274 0 : return true;
275 : }
276 :
277 : template <typename T>
278 : static T*
279 2 : CopyErrorHelper(JSContext* cx, T* report)
280 : {
281 : /*
282 : * We use a single malloc block to make a deep copy of JSErrorReport or
283 : * JSErrorNotes::Note, except JSErrorNotes linked from JSErrorReport with
284 : * the following layout:
285 : * JSErrorReport or JSErrorNotes::Note
286 : * char array with characters for message_
287 : * char array with characters for filename
288 : * char16_t array with characters for linebuf (only for JSErrorReport)
289 : * Such layout together with the properties enforced by the following
290 : * asserts does not need any extra alignment padding.
291 : */
292 : JS_STATIC_ASSERT(sizeof(T) % sizeof(const char*) == 0);
293 : JS_STATIC_ASSERT(sizeof(const char*) % sizeof(char16_t) == 0);
294 :
295 2 : size_t filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
296 2 : size_t messageSize = 0;
297 2 : if (report->message())
298 2 : messageSize = strlen(report->message().c_str()) + 1;
299 :
300 : /*
301 : * The mallocSize can not overflow since it represents the sum of the
302 : * sizes of already allocated objects.
303 : */
304 2 : size_t mallocSize = sizeof(T) + messageSize + filenameSize + ExtraMallocSize(report);
305 2 : uint8_t* cursor = cx->pod_calloc<uint8_t>(mallocSize);
306 2 : if (!cursor)
307 0 : return nullptr;
308 :
309 2 : T* copy = new (cursor) T();
310 2 : cursor += sizeof(T);
311 :
312 2 : if (report->message()) {
313 2 : copy->initBorrowedMessage((const char*)cursor);
314 2 : js_memcpy(cursor, report->message().c_str(), messageSize);
315 2 : cursor += messageSize;
316 : }
317 :
318 2 : if (report->filename) {
319 2 : copy->filename = (const char*)cursor;
320 2 : js_memcpy(cursor, report->filename, filenameSize);
321 2 : cursor += filenameSize;
322 : }
323 :
324 2 : if (!CopyExtraData(cx, &cursor, copy, report)) {
325 : /* js_delete calls destructor for T and js_free for pod_calloc. */
326 0 : js_delete(copy);
327 0 : return nullptr;
328 : }
329 :
330 2 : MOZ_ASSERT(cursor == (uint8_t*)copy + mallocSize);
331 :
332 : /* Copy non-pointer members. */
333 2 : copy->lineno = report->lineno;
334 2 : copy->column = report->column;
335 2 : copy->errorNumber = report->errorNumber;
336 :
337 2 : return copy;
338 : }
339 :
340 : JSErrorNotes::Note*
341 0 : js::CopyErrorNote(JSContext* cx, JSErrorNotes::Note* note)
342 : {
343 0 : return CopyErrorHelper(cx, note);
344 : }
345 :
346 : JSErrorReport*
347 2 : js::CopyErrorReport(JSContext* cx, JSErrorReport* report)
348 : {
349 2 : return CopyErrorHelper(cx, report);
350 : }
351 :
352 : struct SuppressErrorsGuard
353 : {
354 : JSContext* cx;
355 : JS::WarningReporter prevReporter;
356 : JS::AutoSaveExceptionState prevState;
357 :
358 0 : explicit SuppressErrorsGuard(JSContext* cx)
359 0 : : cx(cx),
360 0 : prevReporter(JS::SetWarningReporter(cx, nullptr)),
361 0 : prevState(cx)
362 0 : {}
363 :
364 0 : ~SuppressErrorsGuard()
365 0 : {
366 0 : JS::SetWarningReporter(cx, prevReporter);
367 0 : }
368 : };
369 :
370 : // Cut off the stack if it gets too deep (most commonly for infinite recursion
371 : // errors).
372 : static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
373 :
374 : static bool
375 3 : CaptureStack(JSContext* cx, MutableHandleObject stack)
376 : {
377 : return CaptureCurrentStack(cx, stack,
378 3 : JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)));
379 : }
380 :
381 : JSString*
382 0 : js::ComputeStackString(JSContext* cx)
383 : {
384 0 : SuppressErrorsGuard seg(cx);
385 :
386 0 : RootedObject stack(cx);
387 0 : if (!CaptureStack(cx, &stack))
388 0 : return nullptr;
389 :
390 0 : RootedString str(cx);
391 0 : if (!BuildStackString(cx, stack, &str))
392 0 : return nullptr;
393 :
394 0 : return str.get();
395 : }
396 :
397 : static void
398 0 : exn_finalize(FreeOp* fop, JSObject* obj)
399 : {
400 0 : MOZ_ASSERT(fop->maybeOnHelperThread());
401 0 : if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport())
402 0 : fop->delete_(report);
403 0 : }
404 :
405 : JSErrorReport*
406 0 : js::ErrorFromException(JSContext* cx, HandleObject objArg)
407 : {
408 : // It's ok to UncheckedUnwrap here, since all we do is get the
409 : // JSErrorReport, and consumers are careful with the information they get
410 : // from that anyway. Anyone doing things that would expose anything in the
411 : // JSErrorReport to page script either does a security check on the
412 : // JSErrorReport's principal or also tries to do toString on our object and
413 : // will fail if they can't unwrap it.
414 0 : RootedObject obj(cx, UncheckedUnwrap(objArg));
415 0 : if (!obj->is<ErrorObject>())
416 0 : return nullptr;
417 :
418 0 : JSErrorReport* report = obj->as<ErrorObject>().getOrCreateErrorReport(cx);
419 0 : if (!report) {
420 0 : MOZ_ASSERT(cx->isThrowingOutOfMemory());
421 0 : cx->recoverFromOutOfMemory();
422 : }
423 :
424 0 : return report;
425 : }
426 :
427 : JS_PUBLIC_API(JSObject*)
428 0 : ExceptionStackOrNull(HandleObject objArg)
429 : {
430 0 : JSObject* obj = CheckedUnwrap(objArg);
431 0 : if (!obj || !obj->is<ErrorObject>()) {
432 0 : return nullptr;
433 : }
434 :
435 0 : return obj->as<ErrorObject>().stack();
436 : }
437 :
438 : bool
439 1 : Error(JSContext* cx, unsigned argc, Value* vp)
440 : {
441 1 : CallArgs args = CallArgsFromVp(argc, vp);
442 :
443 : // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
444 2 : RootedObject proto(cx);
445 1 : if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
446 0 : return false;
447 :
448 : /* Compute the error message, if any. */
449 2 : RootedString message(cx, nullptr);
450 1 : if (args.hasDefined(0)) {
451 0 : message = ToString<CanGC>(cx, args[0]);
452 0 : if (!message)
453 0 : return false;
454 : }
455 :
456 : /* Find the scripted caller, but only ones we're allowed to know about. */
457 2 : NonBuiltinFrameIter iter(cx, cx->compartment()->principals());
458 :
459 : /* Set the 'fileName' property. */
460 2 : RootedString fileName(cx);
461 1 : if (args.length() > 1) {
462 0 : fileName = ToString<CanGC>(cx, args[1]);
463 : } else {
464 1 : fileName = cx->runtime()->emptyString;
465 1 : if (!iter.done()) {
466 1 : if (const char* cfilename = iter.filename())
467 1 : fileName = JS_NewStringCopyZ(cx, cfilename);
468 : }
469 : }
470 1 : if (!fileName)
471 0 : return false;
472 :
473 : /* Set the 'lineNumber' property. */
474 1 : uint32_t lineNumber, columnNumber = 0;
475 1 : if (args.length() > 2) {
476 0 : if (!ToUint32(cx, args[2], &lineNumber))
477 0 : return false;
478 : } else {
479 1 : lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
480 : // XXX: Make the column 1-based as in other browsers, instead of 0-based
481 : // which is how SpiderMonkey stores it internally. This will be
482 : // unnecessary once bug 1144340 is fixed.
483 1 : ++columnNumber;
484 : }
485 :
486 2 : RootedObject stack(cx);
487 1 : if (!CaptureStack(cx, &stack))
488 0 : return false;
489 :
490 : /*
491 : * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
492 : * called as functions, without operator new. But as we do not give
493 : * each constructor a distinct JSClass, we must get the exception type
494 : * ourselves.
495 : */
496 1 : JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
497 :
498 2 : RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
499 2 : lineNumber, columnNumber, nullptr, message, proto));
500 1 : if (!obj)
501 0 : return false;
502 :
503 1 : args.rval().setObject(*obj);
504 1 : return true;
505 : }
506 :
507 : #if JS_HAS_TOSOURCE
508 : /*
509 : * Return a string that may eval to something similar to the original object.
510 : */
511 : static bool
512 0 : exn_toSource(JSContext* cx, unsigned argc, Value* vp)
513 : {
514 0 : if (!CheckRecursionLimit(cx))
515 0 : return false;
516 0 : CallArgs args = CallArgsFromVp(argc, vp);
517 :
518 0 : RootedObject obj(cx, ToObject(cx, args.thisv()));
519 0 : if (!obj)
520 0 : return false;
521 :
522 0 : RootedValue nameVal(cx);
523 0 : RootedString name(cx);
524 0 : if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) ||
525 0 : !(name = ToString<CanGC>(cx, nameVal)))
526 : {
527 0 : return false;
528 : }
529 :
530 0 : RootedValue messageVal(cx);
531 0 : RootedString message(cx);
532 0 : if (!GetProperty(cx, obj, obj, cx->names().message, &messageVal) ||
533 0 : !(message = ValueToSource(cx, messageVal)))
534 : {
535 0 : return false;
536 : }
537 :
538 0 : RootedValue filenameVal(cx);
539 0 : RootedString filename(cx);
540 0 : if (!GetProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
541 0 : !(filename = ValueToSource(cx, filenameVal)))
542 : {
543 0 : return false;
544 : }
545 :
546 0 : RootedValue linenoVal(cx);
547 : uint32_t lineno;
548 0 : if (!GetProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
549 0 : !ToUint32(cx, linenoVal, &lineno))
550 : {
551 0 : return false;
552 : }
553 :
554 0 : StringBuffer sb(cx);
555 0 : if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
556 0 : return false;
557 :
558 0 : if (!sb.append(message))
559 0 : return false;
560 :
561 0 : if (!filename->empty()) {
562 0 : if (!sb.append(", ") || !sb.append(filename))
563 0 : return false;
564 : }
565 0 : if (lineno != 0) {
566 : /* We have a line, but no filename, add empty string */
567 0 : if (filename->empty() && !sb.append(", \"\""))
568 0 : return false;
569 :
570 0 : JSString* linenumber = ToString<CanGC>(cx, linenoVal);
571 0 : if (!linenumber)
572 0 : return false;
573 0 : if (!sb.append(", ") || !sb.append(linenumber))
574 0 : return false;
575 : }
576 :
577 0 : if (!sb.append("))"))
578 0 : return false;
579 :
580 0 : JSString* str = sb.finishString();
581 0 : if (!str)
582 0 : return false;
583 0 : args.rval().setString(str);
584 0 : return true;
585 : }
586 : #endif
587 :
588 : /* static */ JSObject*
589 83 : ErrorObject::createProto(JSContext* cx, JSProtoKey key)
590 : {
591 83 : JSExnType type = ExnTypeFromProtoKey(key);
592 :
593 83 : if (type == JSEXN_ERR) {
594 15 : return GlobalObject::createBlankPrototype(cx, cx->global(),
595 15 : &ErrorObject::protoClasses[JSEXN_ERR]);
596 : }
597 :
598 136 : RootedObject protoProto(cx, GlobalObject::getOrCreateErrorPrototype(cx, cx->global()));
599 68 : if (!protoProto)
600 0 : return nullptr;
601 :
602 204 : return GlobalObject::createBlankPrototypeInheriting(cx, cx->global(),
603 68 : &ErrorObject::protoClasses[type],
604 68 : protoProto);
605 : }
606 :
607 : /* static */ JSObject*
608 83 : ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
609 : {
610 83 : JSExnType type = ExnTypeFromProtoKey(key);
611 166 : RootedObject ctor(cx);
612 :
613 83 : if (type == JSEXN_ERR) {
614 15 : ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key);
615 : } else {
616 136 : RootedFunction proto(cx, GlobalObject::getOrCreateErrorConstructor(cx, cx->global()));
617 68 : if (!proto)
618 0 : return nullptr;
619 :
620 136 : ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr,
621 : ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED,
622 68 : SingletonObject);
623 : }
624 :
625 83 : if (!ctor)
626 0 : return nullptr;
627 :
628 83 : ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(type));
629 83 : return ctor;
630 : }
631 :
632 : JS_FRIEND_API(JSFlatString*)
633 0 : js::GetErrorTypeName(JSContext* cx, int16_t exnType)
634 : {
635 : /*
636 : * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
637 : * is prepended before "uncaught exception: "
638 : */
639 0 : if (exnType < 0 || exnType >= JSEXN_LIMIT ||
640 0 : exnType == JSEXN_INTERNALERR || exnType == JSEXN_WARN || exnType == JSEXN_NOTE)
641 : {
642 0 : return nullptr;
643 : }
644 0 : JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
645 0 : return ClassName(key, cx);
646 : }
647 :
648 : void
649 2 : js::ErrorToException(JSContext* cx, JSErrorReport* reportp,
650 : JSErrorCallback callback, void* userRef)
651 : {
652 2 : MOZ_ASSERT(reportp);
653 2 : MOZ_ASSERT(!JSREPORT_IS_WARNING(reportp->flags));
654 :
655 : // We cannot throw a proper object inside the self-hosting compartment, as
656 : // we cannot construct the Error constructor without self-hosted code. Just
657 : // print the error to stderr to help debugging.
658 2 : if (cx->runtime()->isSelfHostingCompartment(cx->compartment())) {
659 0 : PrintError(cx, stderr, JS::ConstUTF8CharsZ(), reportp, true);
660 0 : return;
661 : }
662 :
663 : // Find the exception index associated with this error.
664 2 : JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
665 2 : if (!callback)
666 0 : callback = GetErrorMessage;
667 2 : const JSErrorFormatString* errorString = callback(userRef, errorNumber);
668 2 : JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_ERR;
669 2 : MOZ_ASSERT(exnType < JSEXN_LIMIT);
670 2 : MOZ_ASSERT(exnType != JSEXN_NOTE);
671 :
672 2 : if (exnType == JSEXN_WARN) {
673 : // werror must be enabled, so we use JSEXN_ERR.
674 0 : MOZ_ASSERT(cx->options().werror());
675 0 : exnType = JSEXN_ERR;
676 : }
677 :
678 : // Prevent infinite recursion.
679 2 : if (cx->generatingError)
680 0 : return;
681 4 : AutoScopedAssign<bool> asa(&cx->generatingError.ref(), true);
682 :
683 : // Create an exception object.
684 4 : RootedString messageStr(cx, reportp->newMessageString(cx));
685 2 : if (!messageStr)
686 0 : return;
687 :
688 4 : RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
689 2 : if (!fileName)
690 0 : return;
691 :
692 2 : uint32_t lineNumber = reportp->lineno;
693 2 : uint32_t columnNumber = reportp->column;
694 :
695 4 : RootedObject stack(cx);
696 2 : if (!CaptureStack(cx, &stack))
697 0 : return;
698 :
699 4 : js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
700 2 : if (!report)
701 0 : return;
702 :
703 4 : RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
704 4 : lineNumber, columnNumber, &report, messageStr));
705 2 : if (!errObject)
706 0 : return;
707 :
708 : // Throw it.
709 2 : cx->setPendingException(ObjectValue(*errObject));
710 :
711 : // Flag the error report passed in to indicate an exception was raised.
712 2 : reportp->flags |= JSREPORT_EXCEPTION;
713 : }
714 :
715 : static bool
716 0 : IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject, const char** filename_strp)
717 : {
718 : /*
719 : * This function is called from ErrorReport::init and so should not generate
720 : * any new exceptions.
721 : */
722 0 : AutoClearPendingException acpe(cx);
723 :
724 : bool found;
725 0 : if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
726 0 : return false;
727 :
728 0 : const char* filename_str = *filename_strp;
729 0 : if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
730 : /* Now try "fileName", in case this quacks like an Error */
731 0 : filename_str = js_fileName_str;
732 0 : if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found)
733 0 : return false;
734 : }
735 :
736 0 : if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found)
737 0 : return false;
738 :
739 0 : *filename_strp = filename_str;
740 0 : return true;
741 : }
742 :
743 : static JSString*
744 0 : ErrorReportToString(JSContext* cx, JSErrorReport* reportp)
745 : {
746 : /*
747 : * We do NOT want to use GetErrorTypeName() here because it will not do the
748 : * "right thing" for JSEXN_INTERNALERR. That is, the caller of this API
749 : * expects that "InternalError: " will be prepended but GetErrorTypeName
750 : * goes out of its way to avoid this.
751 : */
752 0 : JSExnType type = static_cast<JSExnType>(reportp->exnType);
753 0 : RootedString str(cx);
754 0 : if (type != JSEXN_WARN && type != JSEXN_NOTE)
755 0 : str = ClassName(GetExceptionProtoKey(type), cx);
756 :
757 : /*
758 : * If "str" is null at this point, that means we just want to use
759 : * message without prefixing it with anything.
760 : */
761 0 : if (str) {
762 0 : RootedString separator(cx, JS_NewUCStringCopyN(cx, u": ", 2));
763 0 : if (!separator)
764 0 : return nullptr;
765 0 : str = ConcatStrings<CanGC>(cx, str, separator);
766 0 : if (!str)
767 0 : return nullptr;
768 : }
769 :
770 0 : RootedString message(cx, reportp->newMessageString(cx));
771 0 : if (!message)
772 0 : return nullptr;
773 :
774 0 : if (!str)
775 0 : return message;
776 :
777 0 : return ConcatStrings<CanGC>(cx, str, message);
778 : }
779 :
780 0 : ErrorReport::ErrorReport(JSContext* cx)
781 : : reportp(nullptr),
782 : str(cx),
783 : strChars(cx),
784 0 : exnObject(cx)
785 : {
786 0 : }
787 :
788 0 : ErrorReport::~ErrorReport()
789 : {
790 0 : }
791 :
792 : void
793 0 : ErrorReport::ReportAddonExceptionToTelemetry(JSContext* cx)
794 : {
795 0 : MOZ_ASSERT(exnObject);
796 0 : RootedObject unwrapped(cx, UncheckedUnwrap(exnObject));
797 0 : MOZ_ASSERT(unwrapped, "UncheckedUnwrap failed?");
798 :
799 : // There is not much we can report if the exception is not an ErrorObject, let's ignore those.
800 0 : if (!unwrapped->is<ErrorObject>())
801 0 : return;
802 :
803 0 : Rooted<ErrorObject*> errObj(cx, &unwrapped->as<ErrorObject>());
804 0 : RootedObject stack(cx, errObj->stack());
805 :
806 : // Let's ignore TOP level exceptions. For regular add-ons those will not be reported anyway,
807 : // for SDK based once it should not be a valid case either.
808 : // At this point the frame stack is unwound but the exception object stored the stack so let's
809 : // use that for getting the function name.
810 0 : if (!stack)
811 0 : return;
812 :
813 0 : JSCompartment* comp = stack->compartment();
814 0 : JSAddonId* addonId = comp->creationOptions().addonIdOrNull();
815 :
816 : // We only want to send the report if the scope that just have thrown belongs to an add-on.
817 : // Let's check the compartment of the youngest function on the stack, to determine that.
818 0 : if (!addonId)
819 0 : return;
820 :
821 0 : RootedString funnameString(cx);
822 0 : JS::SavedFrameResult result = GetSavedFrameFunctionDisplayName(cx, stack, &funnameString);
823 : // AccessDenied should never be the case here for add-ons but let's not risk it.
824 0 : JSAutoByteString bytes;
825 0 : const char* funname = nullptr;
826 0 : bool denied = result == JS::SavedFrameResult::AccessDenied;
827 0 : funname = denied ? "unknown"
828 0 : : funnameString ? AtomToPrintableString(cx,
829 0 : &funnameString->asAtom(),
830 : &bytes)
831 0 : : "anonymous";
832 :
833 0 : UniqueChars addonIdChars(JS_EncodeString(cx, addonId));
834 :
835 0 : const char* filename = nullptr;
836 0 : if (reportp && reportp->filename) {
837 0 : filename = strrchr(reportp->filename, '/');
838 0 : if (filename)
839 0 : filename++;
840 : }
841 0 : if (!filename) {
842 0 : filename = "FILE_NOT_FOUND";
843 : }
844 : char histogramKey[64];
845 0 : SprintfLiteral(histogramKey, "%s %s %s %u",
846 : addonIdChars.get(),
847 : funname,
848 : filename,
849 0 : (reportp ? reportp->lineno : 0) );
850 0 : cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey);
851 : }
852 :
853 : bool
854 0 : ErrorReport::init(JSContext* cx, HandleValue exn,
855 : SniffingBehavior sniffingBehavior)
856 : {
857 0 : MOZ_ASSERT(!cx->isExceptionPending());
858 0 : MOZ_ASSERT(!reportp);
859 :
860 0 : if (exn.isObject()) {
861 : // Because ToString below could error and an exception object could become
862 : // unrooted, we must root our exception object, if any.
863 0 : exnObject = &exn.toObject();
864 0 : reportp = ErrorFromException(cx, exnObject);
865 :
866 0 : if (!reportp && sniffingBehavior == NoSideEffects) {
867 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
868 0 : JSMSG_ERR_DURING_THROW);
869 0 : return false;
870 : }
871 :
872 : // Let's see if the exception is from add-on code, if so, it should be reported
873 : // to telemetry.
874 0 : ReportAddonExceptionToTelemetry(cx);
875 : }
876 :
877 :
878 : // Be careful not to invoke ToString if we've already successfully extracted
879 : // an error report, since the exception might be wrapped in a security
880 : // wrapper, and ToString-ing it might throw.
881 0 : if (reportp) {
882 0 : str = ErrorReportToString(cx, reportp);
883 0 : } else if (exn.isSymbol()) {
884 0 : RootedValue strVal(cx);
885 0 : if (js::SymbolDescriptiveString(cx, exn.toSymbol(), &strVal))
886 0 : str = strVal.toString();
887 : else
888 0 : str = nullptr;
889 : } else {
890 0 : str = ToString<CanGC>(cx, exn);
891 : }
892 :
893 0 : if (!str)
894 0 : cx->clearPendingException();
895 :
896 : // If ErrorFromException didn't get us a JSErrorReport, then the object
897 : // was not an ErrorObject, security-wrapped or otherwise. However, it might
898 : // still quack like one. Give duck-typing a chance. We start by looking for
899 : // "filename" (all lowercase), since that's where DOMExceptions store their
900 : // filename. Then we check "fileName", which is where Errors store it. We
901 : // have to do it in that order, because DOMExceptions have Error.prototype
902 : // on their proto chain, and hence also have a "fileName" property, but its
903 : // value is "".
904 0 : const char* filename_str = "filename";
905 0 : if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
906 : {
907 : // Temporary value for pulling properties off of duck-typed objects.
908 0 : RootedValue val(cx);
909 :
910 0 : RootedString name(cx);
911 0 : if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
912 0 : name = val.toString();
913 : else
914 0 : cx->clearPendingException();
915 :
916 0 : RootedString msg(cx);
917 0 : if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
918 0 : msg = val.toString();
919 : else
920 0 : cx->clearPendingException();
921 :
922 : // If we have the right fields, override the ToString we performed on
923 : // the exception object above with something built out of its quacks
924 : // (i.e. as much of |NameQuack: MessageQuack| as we can make).
925 : //
926 : // It would be nice to use ErrorReportToString here, but we can't quite
927 : // do it - mostly because we'd need to figure out what JSExnType |name|
928 : // corresponds to, which may not be any JSExnType at all.
929 0 : if (name && msg) {
930 0 : RootedString colon(cx, JS_NewStringCopyZ(cx, ": "));
931 0 : if (!colon)
932 0 : return false;
933 0 : RootedString nameColon(cx, ConcatStrings<CanGC>(cx, name, colon));
934 0 : if (!nameColon)
935 0 : return false;
936 0 : str = ConcatStrings<CanGC>(cx, nameColon, msg);
937 0 : if (!str)
938 0 : return false;
939 0 : } else if (name) {
940 0 : str = name;
941 0 : } else if (msg) {
942 0 : str = msg;
943 : }
944 :
945 0 : if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
946 0 : RootedString tmp(cx, ToString<CanGC>(cx, val));
947 0 : if (tmp)
948 0 : filename.encodeUtf8(cx, tmp);
949 : else
950 0 : cx->clearPendingException();
951 : } else {
952 0 : cx->clearPendingException();
953 : }
954 :
955 : uint32_t lineno;
956 0 : if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
957 0 : !ToUint32(cx, val, &lineno))
958 : {
959 0 : cx->clearPendingException();
960 0 : lineno = 0;
961 : }
962 :
963 : uint32_t column;
964 0 : if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
965 0 : !ToUint32(cx, val, &column))
966 : {
967 0 : cx->clearPendingException();
968 0 : column = 0;
969 : }
970 :
971 0 : reportp = &ownedReport;
972 0 : new (reportp) JSErrorReport();
973 0 : ownedReport.filename = filename.ptr();
974 0 : ownedReport.lineno = lineno;
975 0 : ownedReport.exnType = JSEXN_INTERNALERR;
976 0 : ownedReport.column = column;
977 0 : if (str) {
978 : // Note that using |str| for |message_| here is kind of wrong,
979 : // because |str| is supposed to be of the format
980 : // |ErrorName: ErrorMessage|, and |message_| is supposed to
981 : // correspond to |ErrorMessage|. But this is what we've
982 : // historically done for duck-typed error objects.
983 : //
984 : // If only this stuff could get specced one day...
985 : char* utf8;
986 0 : if (str->ensureFlat(cx) &&
987 0 : strChars.initTwoByte(cx, str) &&
988 0 : (utf8 = JS::CharsToNewUTF8CharsZ(cx, strChars.twoByteRange()).c_str()))
989 : {
990 0 : ownedReport.initOwnedMessage(utf8);
991 : } else {
992 0 : cx->clearPendingException();
993 0 : str = nullptr;
994 : }
995 : }
996 : }
997 :
998 0 : const char* utf8Message = nullptr;
999 0 : if (str)
1000 0 : utf8Message = toStringResultBytesStorage.encodeUtf8(cx, str);
1001 0 : if (!utf8Message)
1002 0 : utf8Message = "unknown (can't convert to string)";
1003 :
1004 0 : if (!reportp) {
1005 : // This is basically an inlined version of
1006 : //
1007 : // JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1008 : // JSMSG_UNCAUGHT_EXCEPTION, utf8Message);
1009 : //
1010 : // but without the reporting bits. Instead it just puts all
1011 : // the stuff we care about in our ownedReport and message_.
1012 0 : if (!populateUncaughtExceptionReportUTF8(cx, utf8Message)) {
1013 : // Just give up. We're out of memory or something; not much we can
1014 : // do here.
1015 0 : return false;
1016 : }
1017 : } else {
1018 0 : toStringResult_ = JS::ConstUTF8CharsZ(utf8Message, strlen(utf8Message));
1019 : /* Flag the error as an exception. */
1020 0 : reportp->flags |= JSREPORT_EXCEPTION;
1021 : }
1022 :
1023 0 : return true;
1024 : }
1025 :
1026 : bool
1027 0 : ErrorReport::populateUncaughtExceptionReportUTF8(JSContext* cx, ...)
1028 : {
1029 : va_list ap;
1030 0 : va_start(ap, cx);
1031 0 : bool ok = populateUncaughtExceptionReportUTF8VA(cx, ap);
1032 0 : va_end(ap);
1033 0 : return ok;
1034 : }
1035 :
1036 : bool
1037 0 : ErrorReport::populateUncaughtExceptionReportUTF8VA(JSContext* cx, va_list ap)
1038 : {
1039 0 : new (&ownedReport) JSErrorReport();
1040 0 : ownedReport.flags = JSREPORT_ERROR;
1041 0 : ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION;
1042 : // XXXbz this assumes the stack we have right now is still
1043 : // related to our exception object. It would be better if we
1044 : // could accept a passed-in stack of some sort instead.
1045 0 : NonBuiltinFrameIter iter(cx, cx->compartment()->principals());
1046 0 : if (!iter.done()) {
1047 0 : ownedReport.filename = iter.filename();
1048 0 : ownedReport.lineno = iter.computeLine(&ownedReport.column);
1049 : // XXX: Make the column 1-based as in other browsers, instead of 0-based
1050 : // which is how SpiderMonkey stores it internally. This will be
1051 : // unnecessary once bug 1144340 is fixed.
1052 0 : ++ownedReport.column;
1053 0 : ownedReport.isMuted = iter.mutedErrors();
1054 : }
1055 :
1056 0 : if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr,
1057 : JSMSG_UNCAUGHT_EXCEPTION,
1058 : nullptr, ArgumentsAreUTF8, &ownedReport, ap)) {
1059 0 : return false;
1060 : }
1061 :
1062 0 : toStringResult_ = ownedReport.message();
1063 0 : reportp = &ownedReport;
1064 0 : return true;
1065 : }
1066 :
1067 : JSObject*
1068 0 : js::CopyErrorObject(JSContext* cx, Handle<ErrorObject*> err)
1069 : {
1070 0 : js::ScopedJSFreePtr<JSErrorReport> copyReport;
1071 0 : if (JSErrorReport* errorReport = err->getErrorReport()) {
1072 0 : copyReport = CopyErrorReport(cx, errorReport);
1073 0 : if (!copyReport)
1074 0 : return nullptr;
1075 : }
1076 :
1077 0 : RootedString message(cx, err->getMessage());
1078 0 : if (message && !cx->compartment()->wrap(cx, &message))
1079 0 : return nullptr;
1080 0 : RootedString fileName(cx, err->fileName(cx));
1081 0 : if (!cx->compartment()->wrap(cx, &fileName))
1082 0 : return nullptr;
1083 0 : RootedObject stack(cx, err->stack());
1084 0 : if (!cx->compartment()->wrap(cx, &stack))
1085 0 : return nullptr;
1086 0 : uint32_t lineNumber = err->lineNumber();
1087 0 : uint32_t columnNumber = err->columnNumber();
1088 0 : JSExnType errorType = err->type();
1089 :
1090 : // Create the Error object.
1091 0 : return ErrorObject::create(cx, errorType, stack, fileName,
1092 0 : lineNumber, columnNumber, ©Report, message);
1093 : }
1094 :
1095 : JS_PUBLIC_API(bool)
1096 0 : JS::CreateError(JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName,
1097 : uint32_t lineNumber, uint32_t columnNumber, JSErrorReport* report,
1098 : HandleString message, MutableHandleValue rval)
1099 : {
1100 0 : assertSameCompartment(cx, stack, fileName, message);
1101 0 : AssertObjectIsSavedFrameOrWrapper(cx, stack);
1102 :
1103 0 : js::ScopedJSFreePtr<JSErrorReport> rep;
1104 0 : if (report)
1105 0 : rep = CopyErrorReport(cx, report);
1106 :
1107 : RootedObject obj(cx,
1108 0 : js::ErrorObject::create(cx, type, stack, fileName,
1109 0 : lineNumber, columnNumber, &rep, message));
1110 0 : if (!obj)
1111 0 : return false;
1112 :
1113 0 : rval.setObject(*obj);
1114 0 : return true;
1115 : }
1116 :
1117 : const char*
1118 0 : js::ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes)
1119 : {
1120 0 : if (val.isUndefined())
1121 0 : return "undefined";
1122 :
1123 0 : if (val.isNull())
1124 0 : return "null";
1125 :
1126 0 : AutoClearPendingException acpe(cx);
1127 :
1128 0 : RootedString str(cx, JS_ValueToSource(cx, val));
1129 0 : if (!str)
1130 0 : return "<<error converting value to string>>";
1131 :
1132 0 : StringBuffer sb(cx);
1133 0 : if (val.isObject()) {
1134 0 : RootedObject valObj(cx, val.toObjectOrNull());
1135 : ESClass cls;
1136 0 : if (!GetBuiltinClass(cx, valObj, &cls))
1137 0 : return "<<error determining class of value>>";
1138 : const char* s;
1139 0 : if (cls == ESClass::Array)
1140 0 : s = "the array ";
1141 0 : else if (cls == ESClass::ArrayBuffer)
1142 0 : s = "the array buffer ";
1143 0 : else if (JS_IsArrayBufferViewObject(valObj))
1144 0 : s = "the typed array ";
1145 : else
1146 0 : s = "the object ";
1147 0 : if (!sb.append(s, strlen(s)))
1148 0 : return "<<error converting value to string>>";
1149 0 : } else if (val.isNumber()) {
1150 0 : if (!sb.append("the number "))
1151 0 : return "<<error converting value to string>>";
1152 0 : } else if (val.isString()) {
1153 0 : if (!sb.append("the string "))
1154 0 : return "<<error converting value to string>>";
1155 : } else {
1156 0 : MOZ_ASSERT(val.isBoolean() || val.isSymbol());
1157 0 : return bytes.encodeLatin1(cx, str);
1158 : }
1159 0 : if (!sb.append(str))
1160 0 : return "<<error converting value to string>>";
1161 0 : str = sb.finishString();
1162 0 : if (!str)
1163 0 : return "<<error converting value to string>>";
1164 0 : return bytes.encodeLatin1(cx, str);
1165 : }
1166 :
1167 : bool
1168 0 : js::GetInternalError(JSContext* cx, unsigned errorNumber, MutableHandleValue error)
1169 : {
1170 0 : FixedInvokeArgs<1> args(cx);
1171 0 : args[0].set(Int32Value(errorNumber));
1172 0 : return CallSelfHostedFunction(cx, "GetInternalError", NullHandleValue, args, error);
1173 : }
1174 :
1175 : bool
1176 0 : js::GetTypeError(JSContext* cx, unsigned errorNumber, MutableHandleValue error)
1177 : {
1178 0 : FixedInvokeArgs<1> args(cx);
1179 0 : args[0].set(Int32Value(errorNumber));
1180 0 : return CallSelfHostedFunction(cx, "GetTypeError", NullHandleValue, args, error);
1181 : }
|