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"
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"
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
) {
42 CallWarningReporter(cx
, this);
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
);
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
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
)) {
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
) {
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
)) {
113 fc
->reportError(std::move(err
));
116 void js::ReportCompileErrorLatin1(FrontendContext
* fc
, ErrorMetadata
&& metadata
,
117 UniquePtr
<JSErrorNotes
> notes
,
118 unsigned errorNumber
, ...) {
120 va_start(args
, errorNumber
);
121 ReportCompileErrorLatin1VA(fc
, std::move(metadata
), std::move(notes
),
126 void js::ReportCompileErrorUTF8(FrontendContext
* fc
, ErrorMetadata
&& metadata
,
127 UniquePtr
<JSErrorNotes
> notes
,
128 unsigned errorNumber
, ...) {
130 va_start(args
, errorNumber
);
131 ReportCompileErrorUTF8VA(fc
, std::move(metadata
), std::move(notes
),
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
,
153 MOZ_ASSERT(!cx
->isExceptionPending());
155 // No assertSameCompartment version that doesn't take JSContext...
156 if (error
.isObject()) {
157 AssertSameCompartment(global
, &error
.toObject());
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
);
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();
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());
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
{
207 /* only {0} thru {9} supported */
208 mozilla::Array
<const char*, JS::MaxNumErrorArguments
> args_
;
209 mozilla::Array
<size_t, JS::MaxNumErrorArguments
> lengths_
;
211 bool allocatedElements_
: 1;
214 AutoMessageArgs() : totalLength_(0), count_(0), allocatedElements_(false) {
219 /* free the arguments only if we allocated them */
220 if (allocatedElements_
) {
224 js_free((void*)args_
[i
]);
231 const char* args(size_t i
) const {
232 MOZ_ASSERT(i
< count_
);
236 size_t totalLength() const { return totalLength_
; }
238 size_t lengths(size_t i
) const {
239 MOZ_ASSERT(i
< count_
);
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);
260 for (uint16_t i
= 0; i
< count_
; i
++) {
262 case ArgumentsAreASCII
:
263 case ArgumentsAreUTF8
: {
264 const char* c
= argsArg
? static_cast<const char**>(argsArg
)[i
]
265 : va_arg(ap
, const char*);
267 MOZ_ASSERT_IF(typeArg
== ArgumentsAreASCII
,
268 JS::StringIsASCII(args_
[i
]));
269 lengths_
[i
] = strlen(args_
[i
]);
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();
283 lengths_
[i
] = strlen(utf8
);
284 allocatedElements_
= true;
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();
299 lengths_
[i
] = strlen(utf8
);
300 allocatedElements_
= true;
304 totalLength_
+= lengths_
[i
];
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
,
330 ErrorArgumentsType argumentsType
,
331 T
* reportp
, va_list ap
) {
332 const JSErrorFormatString
* efs
;
335 callback
= GetErrorMessage
;
338 efs
= fc
->gcSafeCallback(callback
, userRef
, errorNumber
);
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
);
355 * Parse the error format, substituting the argument X
356 * for {X} in the format.
362 int expandedArgs
= 0;
364 size_t expandedLength
;
365 size_t len
= strlen(efs
->format
);
367 AutoMessageArgs args
;
368 if (!args
.init(fc
->getAllocator(), messageArgs
, argCount
, argumentsType
,
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 !!!
381 fc
->getAllocator()->pod_malloc
<char>(expandedLength
+ 1);
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
);
403 MOZ_ASSERT(expandedArgs
== args
.count());
406 reportp
->initOwnedMessage(utf8
);
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
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
);
429 snprintf(message
, nbytes
, defaultErrorMessage
, errorNumber
);
430 reportp
->initOwnedMessage(message
);
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
)) {
487 if (!ReportError(cx
, &report
, callback
, userRef
)) {
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
, ...) {
501 va_start(ap
, reportp
);
503 js::ExpandErrorArgumentsVA(fc
, callback
, userRef
, errorNumber
,
504 messageArgs
, argumentsType
, reportp
, ap
);
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
) {
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
,
530 if (!ReportError(cx
, &report
, callback
, userRef
)) {
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
,
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
));
559 ReportOutOfMemory(cx
);
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());
571 MOZ_ASSERT(argumentsType
== ArgumentsAreLatin1
);
572 JS::Latin1Chars
latin1(message
.get(), strlen(message
.get()));
573 JS::UTF8CharsZ
utf8(JS::CharsToNewUTF8CharsZ(cx
, latin1
));
577 report
.initOwnedMessage(reinterpret_cast<const char*>(utf8
.get()));
579 PopulateReportBlame(cx
, &report
);
581 if (!ReportError(cx
, &report
, nullptr, nullptr)) {
585 return report
.isWarning();
588 void js::MaybePrintAndClearPendingException(JSContext
* cx
) {
589 if (!cx
->isExceptionPending()) {
593 AutoClearPendingException
acpe(cx
);
595 JS::ExceptionStack
exnStack(cx
);
596 if (!JS::StealPendingExceptionStack(cx
, &exnStack
)) {
597 fprintf(stderr
, "error getting pending exception\n");
601 JS::ErrorReportBuilder
report(cx
);
602 if (!report
.init(cx
, exnStack
, JS::ErrorReportBuilder::WithSideEffects
)) {
603 fprintf(stderr
, "out of memory initializing JS::ErrorReportBuilder\n");
607 MOZ_ASSERT(!report
.report()->isWarning());
608 JS::PrintError(stderr
, report
, true);