Bug 1874684 - Part 25: Editorial updates. r=dminor
[gecko.git] / js / public / ErrorReport.h
blob9bf824dea26a3c65a1371fdb2ed49d647cea5474
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * Error-reporting APIs.
9 * Despite the types and structures defined here existing in js/public,
10 * significant parts of their heritage date back to distant SpiderMonkey past,
11 * and they are not all universally well-thought-out as ideal,
12 * intended-to-be-permanent API. We may eventually replace this with something
13 * more consistent with ECMAScript the language and less consistent with
14 * '90s-era JSAPI inventions, but it's doubtful this will happen any time soon.
17 #ifndef js_ErrorReport_h
18 #define js_ErrorReport_h
20 #include "mozilla/Assertions.h" // MOZ_ASSERT
21 #include "mozilla/Maybe.h" // mozilla::Maybe
23 #include <cstdarg>
24 #include <iterator> // std::input_iterator_tag, std::iterator
25 #include <stdarg.h>
26 #include <stddef.h> // size_t
27 #include <stdint.h> // int16_t, uint16_t
28 #include <string.h> // strlen
30 #include "jstypes.h" // JS_PUBLIC_API
32 #include "js/AllocPolicy.h"
33 #include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ
34 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
35 #include "js/RootingAPI.h" // JS::HandleObject, JS::RootedObject
36 #include "js/UniquePtr.h" // js::UniquePtr
37 #include "js/Value.h" // JS::Value
38 #include "js/Vector.h" // js::Vector
40 struct JS_PUBLIC_API JSContext;
41 class JS_PUBLIC_API JSString;
43 namespace JS {
44 class ExceptionStack;
46 namespace js {
47 class SystemAllocPolicy;
49 enum ErrorArgumentsType {
50 ArgumentsAreUnicode,
51 ArgumentsAreASCII,
52 ArgumentsAreLatin1,
53 ArgumentsAreUTF8
55 } // namespace js
57 /**
58 * Possible exception types. These types are part of a JSErrorFormatString
59 * structure. They define which error to throw in case of a runtime error.
61 * JSEXN_WARN is used for warnings, that are not strictly errors but are handled
62 * using the generalized error reporting mechanism. (One side effect of this
63 * type is to not prepend 'Error:' to warning messages.) This value can go away
64 * if we ever decide to use an entirely separate mechanism for warnings.
66 enum JSExnType {
67 JSEXN_ERR,
68 JSEXN_FIRST = JSEXN_ERR,
69 JSEXN_INTERNALERR,
70 JSEXN_AGGREGATEERR,
71 JSEXN_EVALERR,
72 JSEXN_RANGEERR,
73 JSEXN_REFERENCEERR,
74 JSEXN_SYNTAXERR,
75 JSEXN_TYPEERR,
76 JSEXN_URIERR,
77 JSEXN_DEBUGGEEWOULDRUN,
78 JSEXN_WASMCOMPILEERROR,
79 JSEXN_WASMLINKERROR,
80 JSEXN_WASMRUNTIMEERROR,
81 JSEXN_ERROR_LIMIT,
82 JSEXN_WARN = JSEXN_ERROR_LIMIT,
83 JSEXN_NOTE,
84 JSEXN_LIMIT
87 struct JSErrorFormatString {
88 /** The error message name in ASCII. */
89 const char* name;
91 /** The error format string in ASCII. */
92 const char* format;
94 /** The number of arguments to expand in the formatted error message. */
95 uint16_t argCount;
97 /** One of the JSExnType constants above. */
98 int16_t exnType;
101 using JSErrorCallback =
102 const JSErrorFormatString* (*)(void* userRef, const unsigned errorNumber);
105 * Base class that implements parts shared by JSErrorReport and
106 * JSErrorNotes::Note.
108 class JSErrorBase {
109 private:
110 // The (default) error message.
111 // If ownsMessage_ is true, the it is freed in destructor.
112 JS::ConstUTF8CharsZ message_;
114 public:
115 // Source file name, URL, etc., or null.
116 JS::ConstUTF8CharsZ filename;
118 // Unique identifier for the script source.
119 unsigned sourceId;
121 // Source line number (1-origin).
122 uint32_t lineno;
124 // Column number in line in UTF-16 code units.
125 JS::ColumnNumberOneOrigin column;
127 // the error number, e.g. see js/public/friend/ErrorNumbers.msg.
128 unsigned errorNumber;
130 // Points to JSErrorFormatString::name.
131 // This string must never be freed.
132 const char* errorMessageName;
134 private:
135 bool ownsMessage_ : 1;
137 public:
138 JSErrorBase()
139 : filename(nullptr),
140 sourceId(0),
141 lineno(0),
142 errorNumber(0),
143 errorMessageName(nullptr),
144 ownsMessage_(false) {}
145 JSErrorBase(JSErrorBase&& other) noexcept
146 : message_(other.message_),
147 filename(other.filename),
148 sourceId(other.sourceId),
149 lineno(other.lineno),
150 column(other.column),
151 errorNumber(other.errorNumber),
152 errorMessageName(other.errorMessageName),
153 ownsMessage_(other.ownsMessage_) {
154 if (ownsMessage_) {
155 other.ownsMessage_ = false;
159 ~JSErrorBase() { freeMessage(); }
161 public:
162 const JS::ConstUTF8CharsZ message() const { return message_; }
164 void initOwnedMessage(const char* messageArg) {
165 initBorrowedMessage(messageArg);
166 ownsMessage_ = true;
168 void initBorrowedMessage(const char* messageArg) {
169 MOZ_ASSERT(!message_);
170 message_ = JS::ConstUTF8CharsZ(messageArg, strlen(messageArg));
173 JSString* newMessageString(JSContext* cx);
175 private:
176 void freeMessage();
180 * Notes associated with JSErrorReport.
182 class JSErrorNotes {
183 public:
184 class Note final : public JSErrorBase {};
186 private:
187 // Stores pointers to each note.
188 js::Vector<js::UniquePtr<Note>, 1, js::SystemAllocPolicy> notes_;
190 bool addNoteVA(js::FrontendContext* fc, const char* filename,
191 unsigned sourceId, uint32_t lineno,
192 JS::ColumnNumberOneOrigin column,
193 JSErrorCallback errorCallback, void* userRef,
194 const unsigned errorNumber,
195 js::ErrorArgumentsType argumentsType, va_list ap);
197 public:
198 JSErrorNotes();
199 ~JSErrorNotes();
201 // Add a note to the given position.
202 bool addNoteASCII(JSContext* cx, const char* filename, unsigned sourceId,
203 uint32_t lineno, JS::ColumnNumberOneOrigin column,
204 JSErrorCallback errorCallback, void* userRef,
205 const unsigned errorNumber, ...);
206 bool addNoteASCII(js::FrontendContext* fc, const char* filename,
207 unsigned sourceId, uint32_t lineno,
208 JS::ColumnNumberOneOrigin column,
209 JSErrorCallback errorCallback, void* userRef,
210 const unsigned errorNumber, ...);
211 bool addNoteLatin1(JSContext* cx, const char* filename, unsigned sourceId,
212 uint32_t lineno, JS::ColumnNumberOneOrigin column,
213 JSErrorCallback errorCallback, void* userRef,
214 const unsigned errorNumber, ...);
215 bool addNoteLatin1(js::FrontendContext* fc, const char* filename,
216 unsigned sourceId, uint32_t lineno,
217 JS::ColumnNumberOneOrigin column,
218 JSErrorCallback errorCallback, void* userRef,
219 const unsigned errorNumber, ...);
220 bool addNoteUTF8(JSContext* cx, const char* filename, unsigned sourceId,
221 uint32_t lineno, JS::ColumnNumberOneOrigin column,
222 JSErrorCallback errorCallback, void* userRef,
223 const unsigned errorNumber, ...);
224 bool addNoteUTF8(js::FrontendContext* fc, const char* filename,
225 unsigned sourceId, uint32_t lineno,
226 JS::ColumnNumberOneOrigin column,
227 JSErrorCallback errorCallback, void* userRef,
228 const unsigned errorNumber, ...);
230 JS_PUBLIC_API size_t length();
232 // Create a deep copy of notes.
233 js::UniquePtr<JSErrorNotes> copy(JSContext* cx);
235 class iterator final {
236 private:
237 js::UniquePtr<Note>* note_;
239 public:
240 using iterator_category = std::input_iterator_tag;
241 using value_type = js::UniquePtr<Note>;
242 using difference_type = ptrdiff_t;
243 using pointer = value_type*;
244 using reference = value_type&;
246 explicit iterator(js::UniquePtr<Note>* note = nullptr) : note_(note) {}
248 bool operator==(iterator other) const { return note_ == other.note_; }
249 bool operator!=(iterator other) const { return !(*this == other); }
250 iterator& operator++() {
251 note_++;
252 return *this;
254 reference operator*() { return *note_; }
257 JS_PUBLIC_API iterator begin();
258 JS_PUBLIC_API iterator end();
262 * Describes a single error or warning that occurs in the execution of script.
264 class JSErrorReport : public JSErrorBase {
265 private:
266 // Offending source line without final '\n'.
267 // If ownsLinebuf_ is true, the buffer is freed in destructor.
268 const char16_t* linebuf_;
270 // Number of chars in linebuf_. Does not include trailing '\0'.
271 size_t linebufLength_;
273 // The 0-based offset of error token in linebuf_.
274 size_t tokenOffset_;
276 public:
277 // Associated notes, or nullptr if there's no note.
278 js::UniquePtr<JSErrorNotes> notes;
280 // One of the JSExnType constants.
281 int16_t exnType;
283 // See the comment in TransitiveCompileOptions.
284 bool isMuted : 1;
286 // This error report is actually a warning.
287 bool isWarning_ : 1;
289 private:
290 bool ownsLinebuf_ : 1;
292 public:
293 JSErrorReport()
294 : linebuf_(nullptr),
295 linebufLength_(0),
296 tokenOffset_(0),
297 notes(nullptr),
298 exnType(0),
299 isMuted(false),
300 isWarning_(false),
301 ownsLinebuf_(false) {}
302 JSErrorReport(JSErrorReport&& other) noexcept
303 : JSErrorBase(std::move(other)),
304 linebuf_(other.linebuf_),
305 linebufLength_(other.linebufLength_),
306 tokenOffset_(other.tokenOffset_),
307 notes(std::move(other.notes)),
308 exnType(other.exnType),
309 isMuted(other.isMuted),
310 isWarning_(other.isWarning_),
311 ownsLinebuf_(other.ownsLinebuf_) {
312 if (ownsLinebuf_) {
313 other.ownsLinebuf_ = false;
317 ~JSErrorReport() { freeLinebuf(); }
319 public:
320 const char16_t* linebuf() const { return linebuf_; }
321 size_t linebufLength() const { return linebufLength_; }
322 size_t tokenOffset() const { return tokenOffset_; }
323 void initOwnedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg,
324 size_t tokenOffsetArg) {
325 initBorrowedLinebuf(linebufArg, linebufLengthArg, tokenOffsetArg);
326 ownsLinebuf_ = true;
328 void initBorrowedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg,
329 size_t tokenOffsetArg);
331 bool isWarning() const { return isWarning_; }
333 private:
334 void freeLinebuf();
337 namespace JS {
339 struct MOZ_STACK_CLASS JS_PUBLIC_API ErrorReportBuilder {
340 explicit ErrorReportBuilder(JSContext* cx);
341 ~ErrorReportBuilder();
343 enum SniffingBehavior { WithSideEffects, NoSideEffects };
346 * Generate a JSErrorReport from the provided thrown value.
348 * If the value is a (possibly wrapped) Error object, the JSErrorReport will
349 * be exactly initialized from the Error object's information, without
350 * observable side effects. (The Error object's JSErrorReport is reused, if
351 * it has one.)
353 * Otherwise various attempts are made to derive JSErrorReport information
354 * from |exnStack| and from the current execution state. This process is
355 * *definitely* inconsistent with any standard, and particulars of the
356 * behavior implemented here generally shouldn't be relied upon.
358 * If the value of |sniffingBehavior| is |WithSideEffects|, some of these
359 * attempts *may* invoke user-configurable behavior when the exception is an
360 * object: converting it to a string, detecting and getting its properties,
361 * accessing its prototype chain, and others are possible. Users *must*
362 * tolerate |ErrorReportBuilder::init| potentially having arbitrary effects.
363 * Any exceptions thrown by these operations will be caught and silently
364 * ignored, and "default" values will be substituted into the JSErrorReport.
366 * But if the value of |sniffingBehavior| is |NoSideEffects|, these attempts
367 * *will not* invoke any observable side effects. The JSErrorReport will
368 * simply contain fewer, less precise details.
370 * Unlike some functions involved in error handling, this function adheres
371 * to the usual JSAPI return value error behavior.
373 bool init(JSContext* cx, const JS::ExceptionStack& exnStack,
374 SniffingBehavior sniffingBehavior);
376 JSErrorReport* report() const { return reportp; }
378 const JS::ConstUTF8CharsZ toStringResult() const { return toStringResult_; }
380 private:
381 // More or less an equivalent of JS_ReportErrorNumber/js::ReportErrorNumberVA
382 // but fills in an ErrorReport instead of reporting it. Uses varargs to
383 // make it simpler to call js::ExpandErrorArgumentsVA.
385 // Returns false if we fail to actually populate the ErrorReport
386 // for some reason (probably out of memory).
387 bool populateUncaughtExceptionReportUTF8(JSContext* cx,
388 JS::HandleObject stack, ...);
389 bool populateUncaughtExceptionReportUTF8VA(JSContext* cx,
390 JS::HandleObject stack,
391 va_list ap);
393 // Reports exceptions from add-on scopes to telemetry.
394 void ReportAddonExceptionToTelemetry(JSContext* cx);
396 // We may have a provided JSErrorReport, so need a way to represent that.
397 JSErrorReport* reportp;
399 // Or we may need to synthesize a JSErrorReport one of our own.
400 JSErrorReport ownedReport;
402 // Root our exception value to keep a possibly borrowed |reportp| alive.
403 JS::RootedObject exnObject;
405 // And for our filename.
406 JS::UniqueChars filename;
408 // We may have a result of error.toString().
409 // FIXME: We should not call error.toString(), since it could have side
410 // effect (see bug 633623).
411 JS::ConstUTF8CharsZ toStringResult_;
412 JS::UniqueChars toStringResultBytesStorage;
415 // Writes a full report to a file descriptor. Does nothing for JSErrorReports
416 // which are warnings, unless reportWarnings is set.
417 extern JS_PUBLIC_API void PrintError(FILE* file, JSErrorReport* report,
418 bool reportWarnings);
420 extern JS_PUBLIC_API void PrintError(FILE* file,
421 const JS::ErrorReportBuilder& builder,
422 bool reportWarnings);
424 } // namespace JS
427 * There are four encoding variants for the error reporting API:
428 * UTF-8
429 * JSAPI's default encoding for error handling. Use this when the encoding
430 * of the error message, format string, and arguments is UTF-8.
431 * ASCII
432 * Equivalent to UTF-8, but also asserts that the error message, format
433 * string, and arguments are all ASCII. Because ASCII is a subset of UTF-8,
434 * any use of this encoding variant *could* be replaced with use of the
435 * UTF-8 variant. This variant exists solely to double-check the
436 * developer's assumption that all these strings truly are ASCII, given that
437 * UTF-8 and ASCII strings regrettably have the same C++ type.
438 * UC = UTF-16
439 * Use this when arguments are UTF-16. The format string must be UTF-8.
440 * Latin1 (planned to be removed)
441 * In this variant, all strings are interpreted byte-for-byte as the
442 * corresponding Unicode codepoint. This encoding may *safely* be used on
443 * any null-terminated string, regardless of its encoding. (You shouldn't
444 * *actually* be uncertain, but in the real world, a string's encoding -- if
445 * promised at all -- may be more...aspirational...than reality.) This
446 * encoding variant will eventually be removed -- work to convert your uses
447 * to UTF-8 as you're able.
450 namespace JS {
451 const uint16_t MaxNumErrorArguments = 10;
455 * Report an exception represented by the sprintf-like conversion of format
456 * and its arguments.
458 extern JS_PUBLIC_API void JS_ReportErrorASCII(JSContext* cx, const char* format,
459 ...) MOZ_FORMAT_PRINTF(2, 3);
461 extern JS_PUBLIC_API void JS_ReportErrorLatin1(JSContext* cx,
462 const char* format, ...)
463 MOZ_FORMAT_PRINTF(2, 3);
465 extern JS_PUBLIC_API void JS_ReportErrorUTF8(JSContext* cx, const char* format,
466 ...) MOZ_FORMAT_PRINTF(2, 3);
469 * Use an errorNumber to retrieve the format string, args are char*
471 extern JS_PUBLIC_API void JS_ReportErrorNumberASCII(
472 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
473 const unsigned errorNumber, ...);
475 extern JS_PUBLIC_API void JS_ReportErrorNumberASCIIVA(
476 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
477 const unsigned errorNumber, va_list ap);
479 extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1(
480 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
481 const unsigned errorNumber, ...);
483 #ifdef va_start
484 extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1VA(
485 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
486 const unsigned errorNumber, va_list ap);
487 #endif
489 extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8(
490 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
491 const unsigned errorNumber, ...);
493 #ifdef va_start
494 extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8VA(
495 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
496 const unsigned errorNumber, va_list ap);
497 #endif
500 * args is null-terminated. That is, a null char* means there are no
501 * more args. The number of args must match the number expected for
502 * errorNumber for the given JSErrorCallback.
504 extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8Array(
505 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
506 const unsigned errorNumber, const char** args);
509 * Use an errorNumber to retrieve the format string, args are char16_t*
511 extern JS_PUBLIC_API void JS_ReportErrorNumberUC(JSContext* cx,
512 JSErrorCallback errorCallback,
513 void* userRef,
514 const unsigned errorNumber,
515 ...);
517 extern JS_PUBLIC_API void JS_ReportErrorNumberUCArray(
518 JSContext* cx, JSErrorCallback errorCallback, void* userRef,
519 const unsigned errorNumber, const char16_t** args);
522 * Complain when out of memory.
524 extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx);
526 extern JS_PUBLIC_API bool JS_ExpandErrorArgumentsASCII(
527 JSContext* cx, JSErrorCallback errorCallback, const unsigned errorNumber,
528 JSErrorReport* reportp, ...);
531 * Complain when an allocation size overflows the maximum supported limit.
533 extern JS_PUBLIC_API void JS_ReportAllocationOverflow(JSContext* cx);
535 namespace JS {
537 extern JS_PUBLIC_API bool CreateError(
538 JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName,
539 uint32_t lineNumber, JS::ColumnNumberOneOrigin column,
540 JSErrorReport* report, HandleString message,
541 Handle<mozilla::Maybe<Value>> cause, MutableHandleValue rval);
543 } /* namespace JS */
545 #endif /* js_ErrorReport_h */