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