Source code

Revision control

Other Tools

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