Bug 1865597 - Add error checking when initializing parallel marking and disable on...
[gecko.git] / js / src / vm / ErrorReporting.cpp
blob82bdf61b661d56226a6e6bb2635454ad2d3df604
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/. */
7 #include "vm/ErrorReporting.h"
9 #include <stdarg.h>
10 #include <utility>
12 #include "jsexn.h"
13 #include "jsfriendapi.h"
15 #include "frontend/FrontendContext.h" // AutoReportFrontendContext
16 #include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ
17 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
18 #include "js/ErrorReport.h" // JSErrorBase
19 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
20 #include "js/Printf.h" // JS_vsmprintf
21 #include "js/Warnings.h" // JS::WarningReporter
22 #include "vm/FrameIter.h"
23 #include "vm/GlobalObject.h"
24 #include "vm/JSContext.h"
26 using namespace js;
28 using JS::HandleObject;
29 using JS::HandleValue;
30 using JS::UniqueTwoByteChars;
32 void js::CallWarningReporter(JSContext* cx, JSErrorReport* reportp) {
33 MOZ_ASSERT(reportp->isWarning());
35 if (JS::WarningReporter warningReporter = cx->runtime()->warningReporter) {
36 warningReporter(cx, reportp);
40 bool js::CompileError::throwError(JSContext* cx) {
41 if (isWarning()) {
42 CallWarningReporter(cx, this);
43 return true;
46 // If there's a runtime exception type associated with this error
47 // number, set that as the pending exception. For errors occurring at
48 // compile time, this is very likely to be a JSEXN_SYNTAXERR.
49 return ErrorToException(cx, this, nullptr, nullptr);
52 bool js::ReportExceptionClosure::operator()(JSContext* cx) {
53 cx->setPendingException(exn_, ShouldCaptureStack::Always);
54 return false;
57 bool js::ReportCompileWarning(FrontendContext* fc, ErrorMetadata&& metadata,
58 UniquePtr<JSErrorNotes> notes,
59 unsigned errorNumber, va_list* args) {
60 // On the main thread, report the error immediately. When compiling off
61 // thread, save the error so that the thread finishing the parse can report
62 // it later.
63 CompileError err;
65 err.notes = std::move(notes);
66 err.isWarning_ = true;
67 err.errorNumber = errorNumber;
69 err.filename = JS::ConstUTF8CharsZ(metadata.filename);
70 err.lineno = metadata.lineNumber;
71 err.column = metadata.columnNumber;
72 err.isMuted = metadata.isMuted;
74 if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) {
75 err.initOwnedLinebuf(lineOfContext.release(), metadata.lineLength,
76 metadata.tokenOffset);
79 if (!ExpandErrorArgumentsVA(fc, GetErrorMessage, nullptr, errorNumber,
80 ArgumentsAreLatin1, &err, *args)) {
81 return false;
84 return fc->reportWarning(std::move(err));
87 static void ReportCompileErrorImpl(FrontendContext* fc,
88 js::ErrorMetadata&& metadata,
89 js::UniquePtr<JSErrorNotes> notes,
90 unsigned errorNumber, va_list* args,
91 ErrorArgumentsType argumentsType) {
92 js::CompileError err;
94 err.notes = std::move(notes);
95 err.isWarning_ = false;
96 err.errorNumber = errorNumber;
98 err.filename = JS::ConstUTF8CharsZ(metadata.filename);
99 err.lineno = metadata.lineNumber;
100 err.column = metadata.columnNumber;
101 err.isMuted = metadata.isMuted;
103 if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) {
104 err.initOwnedLinebuf(lineOfContext.release(), metadata.lineLength,
105 metadata.tokenOffset);
108 if (!js::ExpandErrorArgumentsVA(fc, js::GetErrorMessage, nullptr, errorNumber,
109 argumentsType, &err, *args)) {
110 return;
113 fc->reportError(std::move(err));
116 void js::ReportCompileErrorLatin1(FrontendContext* fc, ErrorMetadata&& metadata,
117 UniquePtr<JSErrorNotes> notes,
118 unsigned errorNumber, ...) {
119 va_list args;
120 va_start(args, errorNumber);
121 ReportCompileErrorLatin1VA(fc, std::move(metadata), std::move(notes),
122 errorNumber, &args);
123 va_end(args);
126 void js::ReportCompileErrorUTF8(FrontendContext* fc, ErrorMetadata&& metadata,
127 UniquePtr<JSErrorNotes> notes,
128 unsigned errorNumber, ...) {
129 va_list args;
130 va_start(args, errorNumber);
131 ReportCompileErrorUTF8VA(fc, std::move(metadata), std::move(notes),
132 errorNumber, &args);
133 va_end(args);
136 void js::ReportCompileErrorLatin1VA(FrontendContext* fc,
137 ErrorMetadata&& metadata,
138 UniquePtr<JSErrorNotes> notes,
139 unsigned errorNumber, va_list* args) {
140 ReportCompileErrorImpl(fc, std::move(metadata), std::move(notes), errorNumber,
141 args, ArgumentsAreLatin1);
144 void js::ReportCompileErrorUTF8VA(FrontendContext* fc, ErrorMetadata&& metadata,
145 UniquePtr<JSErrorNotes> notes,
146 unsigned errorNumber, va_list* args) {
147 ReportCompileErrorImpl(fc, std::move(metadata), std::move(notes), errorNumber,
148 args, ArgumentsAreUTF8);
151 void js::ReportErrorToGlobal(JSContext* cx, Handle<GlobalObject*> global,
152 HandleValue error) {
153 MOZ_ASSERT(!cx->isExceptionPending());
154 #ifdef DEBUG
155 // No assertSameCompartment version that doesn't take JSContext...
156 if (error.isObject()) {
157 AssertSameCompartment(global, &error.toObject());
159 #endif // DEBUG
160 js::ReportExceptionClosure report(error);
161 PrepareScriptEnvironmentAndInvoke(cx, global, report);
164 static bool ReportError(JSContext* cx, JSErrorReport* reportp,
165 JSErrorCallback callback, void* userRef) {
166 if (reportp->isWarning()) {
167 CallWarningReporter(cx, reportp);
168 return true;
171 // Check the error report, and set a JavaScript-catchable exception
172 // if the error is defined to have an associated exception.
173 return ErrorToException(cx, reportp, callback, userRef);
177 * The given JSErrorReport object have been zeroed and must not outlive
178 * cx->fp() (otherwise owned fields may become invalid).
180 static void PopulateReportBlame(JSContext* cx, JSErrorReport* report) {
181 JS::Realm* realm = cx->realm();
182 if (!realm) {
183 return;
187 * Walk stack until we find a frame that is associated with a non-builtin
188 * rather than a builtin frame and which we're allowed to know about.
190 NonBuiltinFrameIter iter(cx, realm->principals());
191 if (iter.done()) {
192 return;
195 report->filename = JS::ConstUTF8CharsZ(iter.filename());
196 if (iter.hasScript()) {
197 report->sourceId = iter.script()->scriptSource()->id();
199 JS::TaggedColumnNumberOneOrigin column;
200 report->lineno = iter.computeLine(&column);
201 report->column = JS::ColumnNumberOneOrigin(column.oneOriginValue());
202 report->isMuted = iter.mutedErrors();
205 class MOZ_RAII AutoMessageArgs {
206 size_t totalLength_;
207 /* only {0} thru {9} supported */
208 mozilla::Array<const char*, JS::MaxNumErrorArguments> args_;
209 mozilla::Array<size_t, JS::MaxNumErrorArguments> lengths_;
210 uint16_t count_;
211 bool allocatedElements_ : 1;
213 public:
214 AutoMessageArgs() : totalLength_(0), count_(0), allocatedElements_(false) {
215 PodArrayZero(args_);
218 ~AutoMessageArgs() {
219 /* free the arguments only if we allocated them */
220 if (allocatedElements_) {
221 uint16_t i = 0;
222 while (i < count_) {
223 if (args_[i]) {
224 js_free((void*)args_[i]);
226 i++;
231 const char* args(size_t i) const {
232 MOZ_ASSERT(i < count_);
233 return args_[i];
236 size_t totalLength() const { return totalLength_; }
238 size_t lengths(size_t i) const {
239 MOZ_ASSERT(i < count_);
240 return lengths_[i];
243 uint16_t count() const { return count_; }
245 /* Gather the arguments into an array, and accumulate their sizes.
247 * We could template on the type of argsArg, but we're already trusting people
248 * to do the right thing with varargs, so might as well trust them on this
249 * part too. Upstream consumers do assert that it's the right thing. Also,
250 * if argsArg were strongly typed we'd still need casting below for this to
251 * compile, because typeArg is not known at compile-time here.
253 template <typename Allocator>
254 bool init(Allocator* alloc, void* argsArg, uint16_t countArg,
255 ErrorArgumentsType typeArg, va_list ap) {
256 MOZ_ASSERT(countArg > 0);
258 count_ = countArg;
260 for (uint16_t i = 0; i < count_; i++) {
261 switch (typeArg) {
262 case ArgumentsAreASCII:
263 case ArgumentsAreUTF8: {
264 const char* c = argsArg ? static_cast<const char**>(argsArg)[i]
265 : va_arg(ap, const char*);
266 args_[i] = c;
267 MOZ_ASSERT_IF(typeArg == ArgumentsAreASCII,
268 JS::StringIsASCII(args_[i]));
269 lengths_[i] = strlen(args_[i]);
270 break;
272 case ArgumentsAreLatin1: {
273 MOZ_ASSERT(!argsArg);
274 const Latin1Char* latin1 = va_arg(ap, Latin1Char*);
275 size_t len = strlen(reinterpret_cast<const char*>(latin1));
276 mozilla::Range<const Latin1Char> range(latin1, len);
277 char* utf8 = JS::CharsToNewUTF8CharsZ(alloc, range).c_str();
278 if (!utf8) {
279 return false;
282 args_[i] = utf8;
283 lengths_[i] = strlen(utf8);
284 allocatedElements_ = true;
285 break;
287 case ArgumentsAreUnicode: {
288 const char16_t* uc = argsArg
289 ? static_cast<const char16_t**>(argsArg)[i]
290 : va_arg(ap, const char16_t*);
291 size_t len = js_strlen(uc);
292 mozilla::Range<const char16_t> range(uc, len);
293 char* utf8 = JS::CharsToNewUTF8CharsZ(alloc, range).c_str();
294 if (!utf8) {
295 return false;
298 args_[i] = utf8;
299 lengths_[i] = strlen(utf8);
300 allocatedElements_ = true;
301 break;
304 totalLength_ += lengths_[i];
306 return true;
311 * The arguments from ap need to be packaged up into an array and stored
312 * into the report struct.
314 * The format string addressed by the error number may contain operands
315 * identified by the format {N}, where N is a decimal digit. Each of these
316 * is to be replaced by the Nth argument from the va_list. The complete
317 * message is placed into reportp->message_.
319 * Returns true if the expansion succeeds (can fail if out of memory).
321 * messageArgs is a `const char**` or a `const char16_t**` but templating on
322 * that is not worth it here because AutoMessageArgs takes a void* anyway, and
323 * using void* here simplifies our callers a bit.
325 template <typename T>
326 static bool ExpandErrorArgumentsHelper(FrontendContext* fc,
327 JSErrorCallback callback, void* userRef,
328 const unsigned errorNumber,
329 void* messageArgs,
330 ErrorArgumentsType argumentsType,
331 T* reportp, va_list ap) {
332 const JSErrorFormatString* efs;
334 if (!callback) {
335 callback = GetErrorMessage;
338 efs = fc->gcSafeCallback(callback, userRef, errorNumber);
340 if (efs) {
341 if constexpr (std::is_same_v<T, JSErrorReport>) {
342 reportp->exnType = efs->exnType;
345 MOZ_ASSERT(reportp->errorNumber == errorNumber);
346 reportp->errorMessageName = efs->name;
348 MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII,
349 JS::StringIsASCII(efs->format));
351 uint16_t argCount = efs->argCount;
352 MOZ_RELEASE_ASSERT(argCount <= JS::MaxNumErrorArguments);
353 if (argCount > 0) {
355 * Parse the error format, substituting the argument X
356 * for {X} in the format.
358 if (efs->format) {
359 const char* fmt;
360 char* out;
361 #ifdef DEBUG
362 int expandedArgs = 0;
363 #endif
364 size_t expandedLength;
365 size_t len = strlen(efs->format);
367 AutoMessageArgs args;
368 if (!args.init(fc->getAllocator(), messageArgs, argCount, argumentsType,
369 ap)) {
370 return false;
373 expandedLength = len - (3 * args.count()) /* exclude the {n} */
374 + args.totalLength();
377 * Note - the above calculation assumes that each argument
378 * is used once and only once in the expansion !!!
380 char* utf8 = out =
381 fc->getAllocator()->pod_malloc<char>(expandedLength + 1);
382 if (!out) {
383 return false;
386 fmt = efs->format;
387 while (*fmt) {
388 if (*fmt == '{') {
389 if (mozilla::IsAsciiDigit(fmt[1])) {
390 int d = AsciiDigitToNumber(fmt[1]);
391 MOZ_RELEASE_ASSERT(d < args.count());
392 strncpy(out, args.args(d), args.lengths(d));
393 out += args.lengths(d);
394 fmt += 3;
395 #ifdef DEBUG
396 expandedArgs++;
397 #endif
398 continue;
401 *out++ = *fmt++;
403 MOZ_ASSERT(expandedArgs == args.count());
404 *out = 0;
406 reportp->initOwnedMessage(utf8);
408 } else {
409 /* Non-null messageArgs should have at least one non-null arg. */
410 MOZ_ASSERT(!messageArgs);
412 * Zero arguments: the format string (if it exists) is the
413 * entire message.
415 if (efs->format) {
416 reportp->initBorrowedMessage(efs->format);
420 if (!reportp->message()) {
421 /* where's the right place for this ??? */
422 const char* defaultErrorMessage =
423 "No error message available for error number %d";
424 size_t nbytes = strlen(defaultErrorMessage) + 16;
425 char* message = fc->getAllocator()->pod_malloc<char>(nbytes);
426 if (!message) {
427 return false;
429 snprintf(message, nbytes, defaultErrorMessage, errorNumber);
430 reportp->initOwnedMessage(message);
432 return true;
435 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
436 void* userRef, const unsigned errorNumber,
437 const char16_t** messageArgs,
438 ErrorArgumentsType argumentsType,
439 JSErrorReport* reportp, va_list ap) {
440 MOZ_ASSERT(argumentsType == ArgumentsAreUnicode);
441 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber,
442 messageArgs, argumentsType, reportp, ap);
445 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
446 void* userRef, const unsigned errorNumber,
447 const char** messageArgs,
448 ErrorArgumentsType argumentsType,
449 JSErrorReport* reportp, va_list ap) {
450 MOZ_ASSERT(argumentsType != ArgumentsAreUnicode);
451 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber,
452 messageArgs, argumentsType, reportp, ap);
455 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
456 void* userRef, const unsigned errorNumber,
457 ErrorArgumentsType argumentsType,
458 JSErrorReport* reportp, va_list ap) {
459 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber, nullptr,
460 argumentsType, reportp, ap);
463 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
464 void* userRef, const unsigned errorNumber,
465 const char16_t** messageArgs,
466 ErrorArgumentsType argumentsType,
467 JSErrorNotes::Note* notep, va_list ap) {
468 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber,
469 messageArgs, argumentsType, notep, ap);
472 bool js::ReportErrorNumberVA(JSContext* cx, IsWarning isWarning,
473 JSErrorCallback callback, void* userRef,
474 const unsigned errorNumber,
475 ErrorArgumentsType argumentsType, va_list ap) {
476 JSErrorReport report;
477 report.isWarning_ = isWarning == IsWarning::Yes;
478 report.errorNumber = errorNumber;
479 PopulateReportBlame(cx, &report);
481 AutoReportFrontendContext fc(cx);
482 if (!ExpandErrorArgumentsVA(&fc, callback, userRef, errorNumber,
483 argumentsType, &report, ap)) {
484 return false;
487 if (!ReportError(cx, &report, callback, userRef)) {
488 return false;
491 return report.isWarning();
494 template <typename CharT>
495 static bool ExpandErrorArguments(FrontendContext* fc, JSErrorCallback callback,
496 void* userRef, const unsigned errorNumber,
497 const CharT** messageArgs,
498 js::ErrorArgumentsType argumentsType,
499 JSErrorReport* reportp, ...) {
500 va_list ap;
501 va_start(ap, reportp);
502 bool expanded =
503 js::ExpandErrorArgumentsVA(fc, callback, userRef, errorNumber,
504 messageArgs, argumentsType, reportp, ap);
505 va_end(ap);
506 return expanded;
509 template <js::ErrorArgumentsType argType, typename CharT>
510 static bool ReportErrorNumberArray(JSContext* cx, IsWarning isWarning,
511 JSErrorCallback callback, void* userRef,
512 const unsigned errorNumber,
513 const CharT** args) {
514 static_assert(
515 (argType == ArgumentsAreUnicode && std::is_same_v<CharT, char16_t>) ||
516 (argType != ArgumentsAreUnicode && std::is_same_v<CharT, char>),
517 "Mismatch between character type and argument type");
519 JSErrorReport report;
520 report.isWarning_ = isWarning == IsWarning::Yes;
521 report.errorNumber = errorNumber;
522 PopulateReportBlame(cx, &report);
524 AutoReportFrontendContext fc(cx);
525 if (!ExpandErrorArguments(&fc, callback, userRef, errorNumber, args, argType,
526 &report)) {
527 return false;
530 if (!ReportError(cx, &report, callback, userRef)) {
531 return false;
534 return report.isWarning();
537 bool js::ReportErrorNumberUCArray(JSContext* cx, IsWarning isWarning,
538 JSErrorCallback callback, void* userRef,
539 const unsigned errorNumber,
540 const char16_t** args) {
541 return ReportErrorNumberArray<ArgumentsAreUnicode>(
542 cx, isWarning, callback, userRef, errorNumber, args);
545 bool js::ReportErrorNumberUTF8Array(JSContext* cx, IsWarning isWarning,
546 JSErrorCallback callback, void* userRef,
547 const unsigned errorNumber,
548 const char** args) {
549 return ReportErrorNumberArray<ArgumentsAreUTF8>(cx, isWarning, callback,
550 userRef, errorNumber, args);
553 bool js::ReportErrorVA(JSContext* cx, IsWarning isWarning, const char* format,
554 js::ErrorArgumentsType argumentsType, va_list ap) {
555 JSErrorReport report;
557 UniqueChars message(JS_vsmprintf(format, ap));
558 if (!message) {
559 ReportOutOfMemory(cx);
560 return false;
563 MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII,
564 JS::StringIsASCII(message.get()));
566 report.isWarning_ = isWarning == IsWarning::Yes;
567 report.errorNumber = JSMSG_USER_DEFINED_ERROR;
568 if (argumentsType == ArgumentsAreASCII || argumentsType == ArgumentsAreUTF8) {
569 report.initOwnedMessage(message.release());
570 } else {
571 MOZ_ASSERT(argumentsType == ArgumentsAreLatin1);
572 JS::Latin1Chars latin1(message.get(), strlen(message.get()));
573 JS::UTF8CharsZ utf8(JS::CharsToNewUTF8CharsZ(cx, latin1));
574 if (!utf8) {
575 return false;
577 report.initOwnedMessage(reinterpret_cast<const char*>(utf8.get()));
579 PopulateReportBlame(cx, &report);
581 if (!ReportError(cx, &report, nullptr, nullptr)) {
582 return false;
585 return report.isWarning();
588 void js::MaybePrintAndClearPendingException(JSContext* cx) {
589 if (!cx->isExceptionPending()) {
590 return;
593 AutoClearPendingException acpe(cx);
595 JS::ExceptionStack exnStack(cx);
596 if (!JS::StealPendingExceptionStack(cx, &exnStack)) {
597 fprintf(stderr, "error getting pending exception\n");
598 return;
601 JS::ErrorReportBuilder report(cx);
602 if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
603 fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n");
604 return;
607 MOZ_ASSERT(!report.report()->isWarning());
608 JS::PrintError(stderr, report, true);