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 #include "vm/ErrorReporting.h"
8
9 #include <stdarg.h>
10 #include <utility>
11
12 #include "jsexn.h"
13 #include "jsfriendapi.h"
14
15 #include "js/Printf.h" // JS_vsmprintf
16 #include "js/Warnings.h" // JS::WarningReporter
17 #include "vm/GlobalObject.h"
18 #include "vm/JSContext.h"
19
20 #include "vm/JSContext-inl.h"
21
22 using namespace js;
23
24 using JS::HandleObject;
25 using JS::HandleValue;
26 using JS::UniqueTwoByteChars;
27
CallWarningReporter(JSContext * cx,JSErrorReport * reportp)28 void js::CallWarningReporter(JSContext* cx, JSErrorReport* reportp) {
29 MOZ_ASSERT(reportp->isWarning());
30
31 if (JS::WarningReporter warningReporter = cx->runtime()->warningReporter) {
32 warningReporter(cx, reportp);
33 }
34 }
35
throwError(JSContext * cx)36 void js::CompileError::throwError(JSContext* cx) {
37 if (isWarning()) {
38 CallWarningReporter(cx, this);
39 return;
40 }
41
42 // If there's a runtime exception type associated with this error
43 // number, set that as the pending exception. For errors occuring at
44 // compile time, this is very likely to be a JSEXN_SYNTAXERR.
45 ErrorToException(cx, this, nullptr, nullptr);
46 }
47
operator ()(JSContext * cx)48 bool js::ReportExceptionClosure::operator()(JSContext* cx) {
49 cx->setPendingExceptionAndCaptureStack(exn_);
50 return false;
51 }
52
ReportCompileWarning(JSContext * cx,ErrorMetadata && metadata,UniquePtr<JSErrorNotes> notes,unsigned errorNumber,va_list * args)53 bool js::ReportCompileWarning(JSContext* cx, ErrorMetadata&& metadata,
54 UniquePtr<JSErrorNotes> notes,
55 unsigned errorNumber, va_list* args) {
56 // On the main thread, report the error immediately. When compiling off
57 // thread, save the error so that the thread finishing the parse can report
58 // it later.
59 CompileError tempErr;
60 CompileError* err = &tempErr;
61 if (cx->isHelperThreadContext() && !cx->addPendingCompileError(&err)) {
62 return false;
63 }
64
65 err->notes = std::move(notes);
66 err->isWarning_ = true;
67 err->errorNumber = errorNumber;
68
69 err->filename = metadata.filename;
70 err->lineno = metadata.lineNumber;
71 err->column = metadata.columnNumber;
72 err->isMuted = metadata.isMuted;
73
74 if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) {
75 err->initOwnedLinebuf(lineOfContext.release(), metadata.lineLength,
76 metadata.tokenOffset);
77 }
78
79 if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr, errorNumber,
80 ArgumentsAreLatin1, err, *args)) {
81 return false;
82 }
83
84 if (!cx->isHelperThreadContext()) {
85 err->throwError(cx);
86 }
87
88 return true;
89 }
90
ReportCompileErrorImpl(JSContext * cx,js::ErrorMetadata && metadata,js::UniquePtr<JSErrorNotes> notes,unsigned errorNumber,va_list * args,ErrorArgumentsType argumentsType)91 static void ReportCompileErrorImpl(JSContext* cx, js::ErrorMetadata&& metadata,
92 js::UniquePtr<JSErrorNotes> notes,
93 unsigned errorNumber, va_list* args,
94 ErrorArgumentsType argumentsType) {
95 // On the main thread, report the error immediately. When compiling off
96 // thread, save the error so that the thread finishing the parse can report
97 // it later.
98 js::CompileError tempErr;
99 js::CompileError* err = &tempErr;
100 if (cx->isHelperThreadContext() && !cx->addPendingCompileError(&err)) {
101 return;
102 }
103
104 err->notes = std::move(notes);
105 err->isWarning_ = false;
106 err->errorNumber = errorNumber;
107
108 err->filename = metadata.filename;
109 err->lineno = metadata.lineNumber;
110 err->column = metadata.columnNumber;
111 err->isMuted = metadata.isMuted;
112
113 if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) {
114 err->initOwnedLinebuf(lineOfContext.release(), metadata.lineLength,
115 metadata.tokenOffset);
116 }
117
118 if (!js::ExpandErrorArgumentsVA(cx, js::GetErrorMessage, nullptr, errorNumber,
119 argumentsType, err, *args)) {
120 return;
121 }
122
123 if (!cx->isHelperThreadContext()) {
124 err->throwError(cx);
125 }
126 }
127
ReportCompileErrorLatin1(JSContext * cx,ErrorMetadata && metadata,UniquePtr<JSErrorNotes> notes,unsigned errorNumber,va_list * args)128 void js::ReportCompileErrorLatin1(JSContext* cx, ErrorMetadata&& metadata,
129 UniquePtr<JSErrorNotes> notes,
130 unsigned errorNumber, va_list* args) {
131 ReportCompileErrorImpl(cx, std::move(metadata), std::move(notes), errorNumber,
132 args, ArgumentsAreLatin1);
133 }
134
ReportCompileErrorUTF8(JSContext * cx,ErrorMetadata && metadata,UniquePtr<JSErrorNotes> notes,unsigned errorNumber,va_list * args)135 void js::ReportCompileErrorUTF8(JSContext* cx, ErrorMetadata&& metadata,
136 UniquePtr<JSErrorNotes> notes,
137 unsigned errorNumber, va_list* args) {
138 ReportCompileErrorImpl(cx, std::move(metadata), std::move(notes), errorNumber,
139 args, ArgumentsAreUTF8);
140 }
141
ReportErrorToGlobal(JSContext * cx,Handle<GlobalObject * > global,HandleValue error)142 void js::ReportErrorToGlobal(JSContext* cx, Handle<GlobalObject*> global,
143 HandleValue error) {
144 MOZ_ASSERT(!cx->isExceptionPending());
145 #ifdef DEBUG
146 // No assertSameCompartment version that doesn't take JSContext...
147 if (error.isObject()) {
148 AssertSameCompartment(global, &error.toObject());
149 }
150 #endif // DEBUG
151 js::ReportExceptionClosure report(error);
152 PrepareScriptEnvironmentAndInvoke(cx, global, report);
153 }
154
ReportError(JSContext * cx,JSErrorReport * reportp,JSErrorCallback callback,void * userRef)155 static void ReportError(JSContext* cx, JSErrorReport* reportp,
156 JSErrorCallback callback, void* userRef) {
157 if (reportp->isWarning()) {
158 CallWarningReporter(cx, reportp);
159 return;
160 }
161
162 // Check the error report, and set a JavaScript-catchable exception
163 // if the error is defined to have an associated exception.
164 ErrorToException(cx, reportp, callback, userRef);
165 }
166
167 /*
168 * The given JSErrorReport object have been zeroed and must not outlive
169 * cx->fp() (otherwise owned fields may become invalid).
170 */
PopulateReportBlame(JSContext * cx,JSErrorReport * report)171 static void PopulateReportBlame(JSContext* cx, JSErrorReport* report) {
172 JS::Realm* realm = cx->realm();
173 if (!realm) {
174 return;
175 }
176
177 /*
178 * Walk stack until we find a frame that is associated with a non-builtin
179 * rather than a builtin frame and which we're allowed to know about.
180 */
181 NonBuiltinFrameIter iter(cx, realm->principals());
182 if (iter.done()) {
183 return;
184 }
185
186 report->filename = iter.filename();
187 if (iter.hasScript()) {
188 report->sourceId = iter.script()->scriptSource()->id();
189 }
190 uint32_t column;
191 report->lineno = iter.computeLine(&column);
192 report->column = FixupColumnForDisplay(column);
193 report->isMuted = iter.mutedErrors();
194 }
195
196 class MOZ_RAII AutoMessageArgs {
197 size_t totalLength_;
198 /* only {0} thru {9} supported */
199 mozilla::Array<const char*, JS::MaxNumErrorArguments> args_;
200 mozilla::Array<size_t, JS::MaxNumErrorArguments> lengths_;
201 uint16_t count_;
202 bool allocatedElements_ : 1;
203
204 public:
AutoMessageArgs()205 AutoMessageArgs() : totalLength_(0), count_(0), allocatedElements_(false) {
206 PodArrayZero(args_);
207 }
208
~AutoMessageArgs()209 ~AutoMessageArgs() {
210 /* free the arguments only if we allocated them */
211 if (allocatedElements_) {
212 uint16_t i = 0;
213 while (i < count_) {
214 if (args_[i]) {
215 js_free((void*)args_[i]);
216 }
217 i++;
218 }
219 }
220 }
221
args(size_t i) const222 const char* args(size_t i) const {
223 MOZ_ASSERT(i < count_);
224 return args_[i];
225 }
226
totalLength() const227 size_t totalLength() const { return totalLength_; }
228
lengths(size_t i) const229 size_t lengths(size_t i) const {
230 MOZ_ASSERT(i < count_);
231 return lengths_[i];
232 }
233
count() const234 uint16_t count() const { return count_; }
235
236 /* Gather the arguments into an array, and accumulate their sizes.
237 *
238 * We could template on the type of argsArg, but we're already trusting people
239 * to do the right thing with varargs, so might as well trust them on this
240 * part too. Upstream consumers do assert that it's the right thing. Also,
241 * if argsArg were strongly typed we'd still need casting below for this to
242 * compile, because typeArg is not known at compile-time here.
243 */
init(JSContext * cx,void * argsArg,uint16_t countArg,ErrorArgumentsType typeArg,va_list ap)244 bool init(JSContext* cx, void* argsArg, uint16_t countArg,
245 ErrorArgumentsType typeArg, va_list ap) {
246 MOZ_ASSERT(countArg > 0);
247
248 count_ = countArg;
249
250 for (uint16_t i = 0; i < count_; i++) {
251 switch (typeArg) {
252 case ArgumentsAreASCII:
253 case ArgumentsAreUTF8: {
254 const char* c = argsArg ? static_cast<const char**>(argsArg)[i]
255 : va_arg(ap, const char*);
256 args_[i] = c;
257 MOZ_ASSERT_IF(typeArg == ArgumentsAreASCII,
258 JS::StringIsASCII(args_[i]));
259 lengths_[i] = strlen(args_[i]);
260 break;
261 }
262 case ArgumentsAreLatin1: {
263 MOZ_ASSERT(!argsArg);
264 const Latin1Char* latin1 = va_arg(ap, Latin1Char*);
265 size_t len = strlen(reinterpret_cast<const char*>(latin1));
266 mozilla::Range<const Latin1Char> range(latin1, len);
267 char* utf8 = JS::CharsToNewUTF8CharsZ(cx, range).c_str();
268 if (!utf8) {
269 return false;
270 }
271
272 args_[i] = utf8;
273 lengths_[i] = strlen(utf8);
274 allocatedElements_ = true;
275 break;
276 }
277 case ArgumentsAreUnicode: {
278 const char16_t* uc = argsArg
279 ? static_cast<const char16_t**>(argsArg)[i]
280 : va_arg(ap, const char16_t*);
281 size_t len = js_strlen(uc);
282 mozilla::Range<const char16_t> range(uc, len);
283 char* utf8 = JS::CharsToNewUTF8CharsZ(cx, range).c_str();
284 if (!utf8) {
285 return false;
286 }
287
288 args_[i] = utf8;
289 lengths_[i] = strlen(utf8);
290 allocatedElements_ = true;
291 break;
292 }
293 }
294 totalLength_ += lengths_[i];
295 }
296 return true;
297 }
298 };
299
300 /*
301 * The arguments from ap need to be packaged up into an array and stored
302 * into the report struct.
303 *
304 * The format string addressed by the error number may contain operands
305 * identified by the format {N}, where N is a decimal digit. Each of these
306 * is to be replaced by the Nth argument from the va_list. The complete
307 * message is placed into reportp->message_.
308 *
309 * Returns true if the expansion succeeds (can fail if out of memory).
310 *
311 * messageArgs is a `const char**` or a `const char16_t**` but templating on
312 * that is not worth it here because AutoMessageArgs takes a void* anyway, and
313 * using void* here simplifies our callers a bit.
314 */
315 template <typename T>
ExpandErrorArgumentsHelper(JSContext * cx,JSErrorCallback callback,void * userRef,const unsigned errorNumber,void * messageArgs,ErrorArgumentsType argumentsType,T * reportp,va_list ap)316 static bool ExpandErrorArgumentsHelper(JSContext* cx, JSErrorCallback callback,
317 void* userRef,
318 const unsigned errorNumber,
319 void* messageArgs,
320 ErrorArgumentsType argumentsType,
321 T* reportp, va_list ap) {
322 const JSErrorFormatString* efs;
323
324 if (!callback) {
325 callback = GetErrorMessage;
326 }
327
328 {
329 gc::AutoSuppressGC suppressGC(cx);
330 efs = callback(userRef, errorNumber);
331 }
332
333 if (efs) {
334 if constexpr (std::is_same_v<T, JSErrorReport>) {
335 reportp->exnType = efs->exnType;
336 }
337
338 MOZ_ASSERT(reportp->errorNumber == errorNumber);
339 reportp->errorMessageName = efs->name;
340
341 MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII,
342 JS::StringIsASCII(efs->format));
343
344 uint16_t argCount = efs->argCount;
345 MOZ_RELEASE_ASSERT(argCount <= JS::MaxNumErrorArguments);
346 if (argCount > 0) {
347 /*
348 * Parse the error format, substituting the argument X
349 * for {X} in the format.
350 */
351 if (efs->format) {
352 const char* fmt;
353 char* out;
354 #ifdef DEBUG
355 int expandedArgs = 0;
356 #endif
357 size_t expandedLength;
358 size_t len = strlen(efs->format);
359
360 AutoMessageArgs args;
361 if (!args.init(cx, messageArgs, argCount, argumentsType, ap)) {
362 return false;
363 }
364
365 expandedLength = len - (3 * args.count()) /* exclude the {n} */
366 + args.totalLength();
367
368 /*
369 * Note - the above calculation assumes that each argument
370 * is used once and only once in the expansion !!!
371 */
372 char* utf8 = out = cx->pod_malloc<char>(expandedLength + 1);
373 if (!out) {
374 return false;
375 }
376
377 fmt = efs->format;
378 while (*fmt) {
379 if (*fmt == '{') {
380 if (mozilla::IsAsciiDigit(fmt[1])) {
381 int d = AsciiDigitToNumber(fmt[1]);
382 MOZ_RELEASE_ASSERT(d < args.count());
383 strncpy(out, args.args(d), args.lengths(d));
384 out += args.lengths(d);
385 fmt += 3;
386 #ifdef DEBUG
387 expandedArgs++;
388 #endif
389 continue;
390 }
391 }
392 *out++ = *fmt++;
393 }
394 MOZ_ASSERT(expandedArgs == args.count());
395 *out = 0;
396
397 reportp->initOwnedMessage(utf8);
398 }
399 } else {
400 /* Non-null messageArgs should have at least one non-null arg. */
401 MOZ_ASSERT(!messageArgs);
402 /*
403 * Zero arguments: the format string (if it exists) is the
404 * entire message.
405 */
406 if (efs->format) {
407 reportp->initBorrowedMessage(efs->format);
408 }
409 }
410 }
411 if (!reportp->message()) {
412 /* where's the right place for this ??? */
413 const char* defaultErrorMessage =
414 "No error message available for error number %d";
415 size_t nbytes = strlen(defaultErrorMessage) + 16;
416 char* message = cx->pod_malloc<char>(nbytes);
417 if (!message) {
418 return false;
419 }
420 snprintf(message, nbytes, defaultErrorMessage, errorNumber);
421 reportp->initOwnedMessage(message);
422 }
423 return true;
424 }
425
ExpandErrorArgumentsVA(JSContext * cx,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const char16_t ** messageArgs,ErrorArgumentsType argumentsType,JSErrorReport * reportp,va_list ap)426 bool js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
427 void* userRef, const unsigned errorNumber,
428 const char16_t** messageArgs,
429 ErrorArgumentsType argumentsType,
430 JSErrorReport* reportp, va_list ap) {
431 MOZ_ASSERT(argumentsType == ArgumentsAreUnicode);
432 return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber,
433 messageArgs, argumentsType, reportp, ap);
434 }
435
ExpandErrorArgumentsVA(JSContext * cx,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const char ** messageArgs,ErrorArgumentsType argumentsType,JSErrorReport * reportp,va_list ap)436 bool js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
437 void* userRef, const unsigned errorNumber,
438 const char** messageArgs,
439 ErrorArgumentsType argumentsType,
440 JSErrorReport* reportp, va_list ap) {
441 MOZ_ASSERT(argumentsType != ArgumentsAreUnicode);
442 return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber,
443 messageArgs, argumentsType, reportp, ap);
444 }
445
ExpandErrorArgumentsVA(JSContext * cx,JSErrorCallback callback,void * userRef,const unsigned errorNumber,ErrorArgumentsType argumentsType,JSErrorReport * reportp,va_list ap)446 bool js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
447 void* userRef, const unsigned errorNumber,
448 ErrorArgumentsType argumentsType,
449 JSErrorReport* reportp, va_list ap) {
450 return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber, nullptr,
451 argumentsType, reportp, ap);
452 }
453
ExpandErrorArgumentsVA(JSContext * cx,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const char16_t ** messageArgs,ErrorArgumentsType argumentsType,JSErrorNotes::Note * notep,va_list ap)454 bool js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
455 void* userRef, const unsigned errorNumber,
456 const char16_t** messageArgs,
457 ErrorArgumentsType argumentsType,
458 JSErrorNotes::Note* notep, va_list ap) {
459 return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber,
460 messageArgs, argumentsType, notep, ap);
461 }
462
ReportErrorNumberVA(JSContext * cx,IsWarning isWarning,JSErrorCallback callback,void * userRef,const unsigned errorNumber,ErrorArgumentsType argumentsType,va_list ap)463 bool js::ReportErrorNumberVA(JSContext* cx, IsWarning isWarning,
464 JSErrorCallback callback, void* userRef,
465 const unsigned errorNumber,
466 ErrorArgumentsType argumentsType, va_list ap) {
467 JSErrorReport report;
468 report.isWarning_ = isWarning == IsWarning::Yes;
469 report.errorNumber = errorNumber;
470 PopulateReportBlame(cx, &report);
471
472 if (!ExpandErrorArgumentsVA(cx, callback, userRef, errorNumber, argumentsType,
473 &report, ap)) {
474 return false;
475 }
476
477 ReportError(cx, &report, callback, userRef);
478
479 return report.isWarning();
480 }
481
482 template <typename CharT>
ExpandErrorArguments(JSContext * cx,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const CharT ** messageArgs,js::ErrorArgumentsType argumentsType,JSErrorReport * reportp,...)483 static bool ExpandErrorArguments(JSContext* cx, JSErrorCallback callback,
484 void* userRef, const unsigned errorNumber,
485 const CharT** messageArgs,
486 js::ErrorArgumentsType argumentsType,
487 JSErrorReport* reportp, ...) {
488 va_list ap;
489 va_start(ap, reportp);
490 bool expanded =
491 js::ExpandErrorArgumentsVA(cx, callback, userRef, errorNumber,
492 messageArgs, argumentsType, reportp, ap);
493 va_end(ap);
494 return expanded;
495 }
496
497 template <js::ErrorArgumentsType argType, typename CharT>
ReportErrorNumberArray(JSContext * cx,IsWarning isWarning,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const CharT ** args)498 static bool ReportErrorNumberArray(JSContext* cx, IsWarning isWarning,
499 JSErrorCallback callback, void* userRef,
500 const unsigned errorNumber,
501 const CharT** args) {
502 static_assert(
503 (argType == ArgumentsAreUnicode && std::is_same_v<CharT, char16_t>) ||
504 (argType != ArgumentsAreUnicode && std::is_same_v<CharT, char>),
505 "Mismatch between character type and argument type");
506
507 JSErrorReport report;
508 report.isWarning_ = isWarning == IsWarning::Yes;
509 report.errorNumber = errorNumber;
510 PopulateReportBlame(cx, &report);
511
512 if (!ExpandErrorArguments(cx, callback, userRef, errorNumber, args, argType,
513 &report)) {
514 return false;
515 }
516
517 ReportError(cx, &report, callback, userRef);
518
519 return report.isWarning();
520 }
521
ReportErrorNumberUCArray(JSContext * cx,IsWarning isWarning,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const char16_t ** args)522 bool js::ReportErrorNumberUCArray(JSContext* cx, IsWarning isWarning,
523 JSErrorCallback callback, void* userRef,
524 const unsigned errorNumber,
525 const char16_t** args) {
526 return ReportErrorNumberArray<ArgumentsAreUnicode>(
527 cx, isWarning, callback, userRef, errorNumber, args);
528 }
529
ReportErrorNumberUTF8Array(JSContext * cx,IsWarning isWarning,JSErrorCallback callback,void * userRef,const unsigned errorNumber,const char ** args)530 bool js::ReportErrorNumberUTF8Array(JSContext* cx, IsWarning isWarning,
531 JSErrorCallback callback, void* userRef,
532 const unsigned errorNumber,
533 const char** args) {
534 return ReportErrorNumberArray<ArgumentsAreUTF8>(cx, isWarning, callback,
535 userRef, errorNumber, args);
536 }
537
ReportErrorVA(JSContext * cx,IsWarning isWarning,const char * format,js::ErrorArgumentsType argumentsType,va_list ap)538 bool js::ReportErrorVA(JSContext* cx, IsWarning isWarning, const char* format,
539 js::ErrorArgumentsType argumentsType, va_list ap) {
540 JSErrorReport report;
541
542 UniqueChars message(JS_vsmprintf(format, ap));
543 if (!message) {
544 ReportOutOfMemory(cx);
545 return false;
546 }
547
548 MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII,
549 JS::StringIsASCII(message.get()));
550
551 report.isWarning_ = isWarning == IsWarning::Yes;
552 report.errorNumber = JSMSG_USER_DEFINED_ERROR;
553 if (argumentsType == ArgumentsAreASCII || argumentsType == ArgumentsAreUTF8) {
554 report.initOwnedMessage(message.release());
555 } else {
556 MOZ_ASSERT(argumentsType == ArgumentsAreLatin1);
557 Latin1Chars latin1(message.get(), strlen(message.get()));
558 UTF8CharsZ utf8(JS::CharsToNewUTF8CharsZ(cx, latin1));
559 if (!utf8) {
560 return false;
561 }
562 report.initOwnedMessage(reinterpret_cast<const char*>(utf8.get()));
563 }
564 PopulateReportBlame(cx, &report);
565
566 ReportError(cx, &report, nullptr, nullptr);
567
568 return report.isWarning();
569 }
570