/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * JS standard exception implementation. */ #include "jsexn.h" #include "mozilla/ArrayUtils.h" #include "mozilla/PodOperations.h" #include #include "jsapi.h" #include "jscntxt.h" #include "jsfun.h" #include "jsnum.h" #include "jsobj.h" #include "jsprf.h" #include "jsscript.h" #include "jstypes.h" #include "jsutil.h" #include "jswrapper.h" #include "gc/Marking.h" #include "vm/ErrorObject.h" #include "vm/GlobalObject.h" #include "vm/SavedStacks.h" #include "vm/StringBuffer.h" #include "jsobjinlines.h" #include "vm/ErrorObject-inl.h" #include "vm/SavedStacks-inl.h" using namespace js; using namespace js::gc; using mozilla::ArrayLength; using mozilla::PodArrayZero; static void exn_finalize(FreeOp* fop, JSObject* obj); bool Error(JSContext* cx, unsigned argc, Value* vp); static bool exn_toSource(JSContext* cx, unsigned argc, Value* vp); static const JSPropertySpec exception_properties[] = { JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0), JS_PS_END }; static const JSFunctionSpec exception_methods[] = { #if JS_HAS_TOSOURCE JS_FN(js_toSource_str, exn_toSource, 0, 0), #endif JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0), JS_FS_END }; #define IMPLEMENT_ERROR_SUBCLASS(name) \ { \ js_Error_str, /* yes, really */ \ JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \ JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS), \ nullptr, /* addProperty */ \ nullptr, /* delProperty */ \ nullptr, /* getProperty */ \ nullptr, /* setProperty */ \ nullptr, /* enumerate */ \ nullptr, /* resolve */ \ nullptr, /* mayResolve */ \ exn_finalize, \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ nullptr, /* construct */ \ nullptr, /* trace */ \ { \ ErrorObject::createConstructor, \ ErrorObject::createProto, \ nullptr, \ nullptr, \ exception_methods, \ exception_properties, \ nullptr, \ JSProto_Error \ } \ } const Class ErrorObject::classes[JSEXN_LIMIT] = { { js_Error_str, JSCLASS_HAS_CACHED_PROTO(JSProto_Error) | JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS), nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ exn_finalize, nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ { ErrorObject::createConstructor, ErrorObject::createProto, nullptr, nullptr, exception_methods, exception_properties, nullptr } }, IMPLEMENT_ERROR_SUBCLASS(InternalError), IMPLEMENT_ERROR_SUBCLASS(EvalError), IMPLEMENT_ERROR_SUBCLASS(RangeError), IMPLEMENT_ERROR_SUBCLASS(ReferenceError), IMPLEMENT_ERROR_SUBCLASS(SyntaxError), IMPLEMENT_ERROR_SUBCLASS(TypeError), IMPLEMENT_ERROR_SUBCLASS(URIError) }; JSErrorReport* js::CopyErrorReport(JSContext* cx, JSErrorReport* report) { /* * We use a single malloc block to make a deep copy of JSErrorReport with * the following layout: * JSErrorReport * array of copies of report->messageArgs * char16_t array with characters for all messageArgs * char16_t array with characters for ucmessage * char16_t array with characters for linebuf * char array with characters for filename * Such layout together with the properties enforced by the following * asserts does not need any extra alignment padding. */ JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char*) == 0); JS_STATIC_ASSERT(sizeof(const char*) % sizeof(char16_t) == 0); #define JS_CHARS_SIZE(chars) ((js_strlen(chars) + 1) * sizeof(char16_t)) size_t filenameSize = report->filename ? strlen(report->filename) + 1 : 0; size_t linebufSize = 0; if (report->linebuf()) linebufSize = (report->linebufLength() + 1) * sizeof(char16_t); size_t ucmessageSize = 0; size_t argsArraySize = 0; size_t argsCopySize = 0; if (report->ucmessage) { ucmessageSize = JS_CHARS_SIZE(report->ucmessage); if (report->messageArgs) { size_t i = 0; for (; report->messageArgs[i]; ++i) argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]); /* Non-null messageArgs should have at least one non-null arg. */ MOZ_ASSERT(i != 0); argsArraySize = (i + 1) * sizeof(const char16_t*); } } /* * The mallocSize can not overflow since it represents the sum of the * sizes of already allocated objects. */ size_t mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize + ucmessageSize + linebufSize + filenameSize; uint8_t* cursor = cx->pod_calloc(mallocSize); if (!cursor) return nullptr; JSErrorReport* copy = (JSErrorReport*)cursor; cursor += sizeof(JSErrorReport); if (argsArraySize != 0) { copy->messageArgs = (const char16_t**)cursor; cursor += argsArraySize; size_t i = 0; for (; report->messageArgs[i]; ++i) { copy->messageArgs[i] = (const char16_t*)cursor; size_t argSize = JS_CHARS_SIZE(report->messageArgs[i]); js_memcpy(cursor, report->messageArgs[i], argSize); cursor += argSize; } copy->messageArgs[i] = nullptr; MOZ_ASSERT(cursor == (uint8_t*)copy->messageArgs[0] + argsCopySize); } if (report->ucmessage) { copy->ucmessage = (const char16_t*)cursor; js_memcpy(cursor, report->ucmessage, ucmessageSize); cursor += ucmessageSize; } if (report->linebuf()) { const char16_t* linebufCopy = (const char16_t*)cursor; js_memcpy(cursor, report->linebuf(), linebufSize); cursor += linebufSize; copy->initLinebuf(linebufCopy, report->linebufLength(), report->tokenOffset()); } if (report->filename) { copy->filename = (const char*)cursor; js_memcpy(cursor, report->filename, filenameSize); } MOZ_ASSERT(cursor + filenameSize == (uint8_t*)copy + mallocSize); /* Copy non-pointer members. */ copy->isMuted = report->isMuted; copy->lineno = report->lineno; copy->column = report->column; copy->errorNumber = report->errorNumber; copy->exnType = report->exnType; /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */ copy->flags = report->flags; #undef JS_CHARS_SIZE return copy; } struct SuppressErrorsGuard { JSContext* cx; JSErrorReporter prevReporter; JS::AutoSaveExceptionState prevState; explicit SuppressErrorsGuard(JSContext* cx) : cx(cx), prevReporter(JS_SetErrorReporter(cx->runtime(), nullptr)), prevState(cx) {} ~SuppressErrorsGuard() { JS_SetErrorReporter(cx->runtime(), prevReporter); } }; // Cut off the stack if it gets too deep (most commonly for infinite recursion // errors). static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; static bool CaptureStack(JSContext* cx, MutableHandleObject stack) { return CaptureCurrentStack(cx, stack, MAX_REPORTED_STACK_DEPTH); } JSString* js::ComputeStackString(JSContext* cx) { SuppressErrorsGuard seg(cx); RootedObject stack(cx); if (!CaptureStack(cx, &stack)) return nullptr; RootedString str(cx); if (!BuildStackString(cx, stack, &str)) return nullptr; return str.get(); } static void exn_finalize(FreeOp* fop, JSObject* obj) { if (JSErrorReport* report = obj->as().getErrorReport()) fop->free_(report); } JSErrorReport* js::ErrorFromException(JSContext* cx, HandleObject objArg) { // It's ok to UncheckedUnwrap here, since all we do is get the // JSErrorReport, and consumers are careful with the information they get // from that anyway. Anyone doing things that would expose anything in the // JSErrorReport to page script either does a security check on the // JSErrorReport's principal or also tries to do toString on our object and // will fail if they can't unwrap it. RootedObject obj(cx, UncheckedUnwrap(objArg)); if (!obj->is()) return nullptr; return obj->as().getOrCreateErrorReport(cx); } JS_PUBLIC_API(JSObject*) ExceptionStackOrNull(JSContext* cx, HandleObject objArg) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, objArg); RootedObject obj(cx, CheckedUnwrap(objArg)); if (!obj || !obj->is()) { return nullptr; } return obj->as().stack(); } bool Error(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString RootedObject proto(cx); if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; /* Compute the error message, if any. */ RootedString message(cx, nullptr); if (args.hasDefined(0)) { message = ToString(cx, args[0]); if (!message) return false; } /* Find the scripted caller, but only ones we're allowed to know about. */ NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); /* Set the 'fileName' property. */ RootedString fileName(cx); if (args.length() > 1) { fileName = ToString(cx, args[1]); } else { fileName = cx->runtime()->emptyString; if (!iter.done()) { if (const char* cfilename = iter.scriptFilename()) fileName = JS_NewStringCopyZ(cx, cfilename); } } if (!fileName) return false; /* Set the 'lineNumber' property. */ uint32_t lineNumber, columnNumber = 0; if (args.length() > 2) { if (!ToUint32(cx, args[2], &lineNumber)) return false; } else { lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber); // XXX: Make the column 1-based as in other browsers, instead of 0-based // which is how SpiderMonkey stores it internally. This will be // unnecessary once bug 1144340 is fixed. ++columnNumber; } RootedObject stack(cx); if (!CaptureStack(cx, &stack)) return false; /* * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when * called as functions, without operator new. But as we do not give * each constructor a distinct JSClass, we must get the exception type * ourselves. */ JSExnType exnType = JSExnType(args.callee().as().getExtendedSlot(0).toInt32()); RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName, lineNumber, columnNumber, nullptr, message, proto)); if (!obj) return false; args.rval().setObject(*obj); return true; } #if JS_HAS_TOSOURCE /* * Return a string that may eval to something similar to the original object. */ static bool exn_toSource(JSContext* cx, unsigned argc, Value* vp) { JS_CHECK_RECURSION(cx, return false); CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; RootedValue nameVal(cx); RootedString name(cx); if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) || !(name = ToString(cx, nameVal))) { return false; } RootedValue messageVal(cx); RootedString message(cx); if (!GetProperty(cx, obj, obj, cx->names().message, &messageVal) || !(message = ValueToSource(cx, messageVal))) { return false; } RootedValue filenameVal(cx); RootedString filename(cx); if (!GetProperty(cx, obj, obj, cx->names().fileName, &filenameVal) || !(filename = ValueToSource(cx, filenameVal))) { return false; } RootedValue linenoVal(cx); uint32_t lineno; if (!GetProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) || !ToUint32(cx, linenoVal, &lineno)) { return false; } StringBuffer sb(cx); if (!sb.append("(new ") || !sb.append(name) || !sb.append("(")) return false; if (!sb.append(message)) return false; if (!filename->empty()) { if (!sb.append(", ") || !sb.append(filename)) return false; } if (lineno != 0) { /* We have a line, but no filename, add empty string */ if (filename->empty() && !sb.append(", \"\"")) return false; JSString* linenumber = ToString(cx, linenoVal); if (!linenumber) return false; if (!sb.append(", ") || !sb.append(linenumber)) return false; } if (!sb.append("))")) return false; JSString* str = sb.finishString(); if (!str) return false; args.rval().setString(str); return true; } #endif /* static */ JSObject* ErrorObject::createProto(JSContext* cx, JSProtoKey key) { RootedObject errorProto(cx, GenericCreatePrototype(cx, key)); if (!errorProto) return nullptr; Rooted err(cx, &errorProto->as()); RootedString emptyStr(cx, cx->names().empty); JSExnType type = ExnTypeFromProtoKey(key); if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, nullptr, 0, 0, emptyStr)) return nullptr; // The various prototypes also have .name in addition to the normal error // instance properties. RootedPropertyName name(cx, ClassName(key, cx)); RootedValue nameValue(cx, StringValue(name)); if (!DefineProperty(cx, err, cx->names().name, nameValue, nullptr, nullptr, 0)) return nullptr; return errorProto; } /* static */ JSObject* ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) { RootedObject ctor(cx); ctor = GenericCreateConstructor(cx, key); if (!ctor) return nullptr; ctor->as().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key))); return ctor; } JS_FRIEND_API(JSFlatString*) js::GetErrorTypeName(JSRuntime* rt, int16_t exnType) { /* * JSEXN_INTERNALERR returns null to prevent that "InternalError: " * is prepended before "uncaught exception: " */ if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT || exnType == JSEXN_INTERNALERR) { return nullptr; } JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType)); return ClassName(key, rt); } bool js::ErrorToException(JSContext* cx, const char* message, JSErrorReport* reportp, JSErrorCallback callback, void* userRef) { // Tell our caller to report immediately if this report is just a warning. MOZ_ASSERT(reportp); if (JSREPORT_IS_WARNING(reportp->flags)) return false; // Similarly, we cannot throw a proper object inside the self-hosting // compartment, as we cannot construct the Error constructor without // self-hosted code. Tell our caller to report immediately. // Without self-hosted code, we cannot get started anyway. if (cx->runtime()->isSelfHostingCompartment(cx->compartment())) return false; // Find the exception index associated with this error. JSErrNum errorNumber = static_cast(reportp->errorNumber); if (!callback) callback = GetErrorMessage; const JSErrorFormatString* errorString = callback(userRef, errorNumber); JSExnType exnType = errorString ? static_cast(errorString->exnType) : JSEXN_NONE; MOZ_ASSERT(exnType < JSEXN_LIMIT); // Return false (no exception raised) if no exception is associated // with the given error number. if (exnType == JSEXN_NONE) return false; // Prevent infinite recursion. if (cx->generatingError) return false; AutoScopedAssign asa(&cx->generatingError, true); // Create an exception object. RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage) : JS_NewStringCopyZ(cx, message)); if (!messageStr) return cx->isExceptionPending(); RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename)); if (!fileName) return cx->isExceptionPending(); uint32_t lineNumber = reportp->lineno; uint32_t columnNumber = reportp->column; RootedObject stack(cx); if (!CaptureStack(cx, &stack)) return cx->isExceptionPending(); js::ScopedJSFreePtr report(CopyErrorReport(cx, reportp)); if (!report) return cx->isExceptionPending(); RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName, lineNumber, columnNumber, &report, messageStr)); if (!errObject) return cx->isExceptionPending(); // Throw it. RootedValue errValue(cx, ObjectValue(*errObject)); JS_SetPendingException(cx, errValue); // Flag the error report passed in to indicate an exception was raised. reportp->flags |= JSREPORT_EXCEPTION; return true; } static bool IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject, const char** filename_strp) { /* * This function is called from ErrorReport::init and so should not generate * any new exceptions. */ AutoClearPendingException acpe(cx); bool found; if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found) return false; const char* filename_str = *filename_strp; if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) { /* Now try "fileName", in case this quacks like an Error */ filename_str = js_fileName_str; if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) return false; } if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found) return false; *filename_strp = filename_str; return true; } JS_FRIEND_API(JSString*) js::ErrorReportToString(JSContext* cx, JSErrorReport* reportp) { JSExnType type = static_cast(reportp->exnType); RootedString str(cx, cx->runtime()->emptyString); if (type != JSEXN_NONE) str = ClassName(GetExceptionProtoKey(type), cx); RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2)); if (!str || !toAppend) return nullptr; str = ConcatStrings(cx, str, toAppend); if (!str) return nullptr; toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage); if (toAppend) str = ConcatStrings(cx, str, toAppend); return str; } bool js::ReportUncaughtException(JSContext* cx) { if (!cx->isExceptionPending()) return true; RootedValue exn(cx); if (!cx->getPendingException(&exn)) { cx->clearPendingException(); return false; } cx->clearPendingException(); ErrorReport err(cx); if (!err.init(cx, exn)) { cx->clearPendingException(); return false; } cx->setPendingException(exn); CallErrorReporter(cx, err.message(), err.report()); cx->clearPendingException(); return true; } ErrorReport::ErrorReport(JSContext* cx) : reportp(nullptr), message_(nullptr), ownedMessage(nullptr), str(cx), strChars(cx), exnObject(cx) { } ErrorReport::~ErrorReport() { if (!ownedMessage) return; js_free(ownedMessage); if (ownedReport.messageArgs) { /* * ExpandErrorArgumentsVA owns its messageArgs only if it had to * inflate the arguments (from regular |char*|s), which is always in * our case. */ size_t i = 0; while (ownedReport.messageArgs[i]) js_free(const_cast(ownedReport.messageArgs[i++])); js_free(ownedReport.messageArgs); } js_free(const_cast(ownedReport.ucmessage)); } void ErrorReport::ReportAddonExceptionToTelementry(JSContext* cx) { MOZ_ASSERT(exnObject); RootedObject unwrapped(cx, UncheckedUnwrap(exnObject)); MOZ_ASSERT(unwrapped, "UncheckedUnwrap failed?"); // There is not much we can report if the exception is not an ErrorObject, let's ignore those. if (!unwrapped->is()) return; Rooted errObj(cx, &unwrapped->as()); RootedObject stack(cx, errObj->stack()); // Let's ignore TOP level exceptions. For regular add-ons those will not be reported anyway, // for SDK based once it should not be a valid case either. // At this point the frame stack is unwound but the exception object stored the stack so let's // use that for getting the function name. if (!stack) return; JSCompartment* comp = stack->compartment(); JSAddonId* addonId = comp->addonId; // We only want to send the report if the scope that just have thrown belongs to an add-on. // Let's check the compartment of the youngest function on the stack, to determine that. if (!addonId) return; RootedString funnameString(cx); JS::SavedFrameResult result = GetSavedFrameFunctionDisplayName(cx, stack, &funnameString); // AccessDenied should never be the case here for add-ons but let's not risk it. JSAutoByteString bytes; const char* funname = nullptr; bool denied = result == JS::SavedFrameResult::AccessDenied; funname = denied ? "unknown" : funnameString ? AtomToPrintableString(cx, &funnameString->asAtom(), &bytes) : "anonymous"; UniqueChars addonIdChars(JS_EncodeString(cx, addonId)); const char* filename = nullptr; if (reportp && reportp->filename) { filename = strrchr(reportp->filename, '/'); if (filename) filename++; } if (!filename) { filename = "FILE_NOT_FOUND"; } char histogramKey[64]; JS_snprintf(histogramKey, sizeof(histogramKey), "%s %s %s %u", addonIdChars.get(), funname, filename, (reportp ? reportp->lineno : 0) ); cx->runtime()->addTelemetry(JS_TELEMETRY_ADDON_EXCEPTIONS, 1, histogramKey); } bool ErrorReport::init(JSContext* cx, HandleValue exn) { MOZ_ASSERT(!cx->isExceptionPending()); if (exn.isObject()) { // Because ToString below could error and an exception object could become // unrooted, we must root our exception object, if any. exnObject = &exn.toObject(); reportp = ErrorFromException(cx, exnObject); // Let's see if the exception is from add-on code, if so, it should be reported // to telementry. ReportAddonExceptionToTelementry(cx); } // Be careful not to invoke ToString if we've already successfully extracted // an error report, since the exception might be wrapped in a security // wrapper, and ToString-ing it might throw. if (reportp) str = ErrorReportToString(cx, reportp); else str = ToString(cx, exn); if (!str) cx->clearPendingException(); // If ErrorFromException didn't get us a JSErrorReport, then the object // was not an ErrorObject, security-wrapped or otherwise. However, it might // still quack like one. Give duck-typing a chance. We start by looking for // "filename" (all lowercase), since that's where DOMExceptions store their // filename. Then we check "fileName", which is where Errors store it. We // have to do it in that order, because DOMExceptions have Error.prototype // on their proto chain, and hence also have a "fileName" property, but its // value is "". const char* filename_str = "filename"; if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str)) { // Temporary value for pulling properties off of duck-typed objects. RootedValue val(cx); RootedString name(cx); if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString()) name = val.toString(); else cx->clearPendingException(); RootedString msg(cx); if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString()) msg = val.toString(); else cx->clearPendingException(); // If we have the right fields, override the ToString we performed on // the exception object above with something built out of its quacks // (i.e. as much of |NameQuack: MessageQuack| as we can make). // // It would be nice to use ErrorReportToString here, but we can't quite // do it - mostly because we'd need to figure out what JSExnType |name| // corresponds to, which may not be any JSExnType at all. if (name && msg) { RootedString colon(cx, JS_NewStringCopyZ(cx, ": ")); if (!colon) return false; RootedString nameColon(cx, ConcatStrings(cx, name, colon)); if (!nameColon) return false; str = ConcatStrings(cx, nameColon, msg); if (!str) return false; } else if (name) { str = name; } else if (msg) { str = msg; } if (JS_GetProperty(cx, exnObject, filename_str, &val)) { RootedString tmp(cx, ToString(cx, val)); if (tmp) filename.encodeUtf8(cx, tmp); else cx->clearPendingException(); } else { cx->clearPendingException(); } uint32_t lineno; if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) || !ToUint32(cx, val, &lineno)) { cx->clearPendingException(); lineno = 0; } uint32_t column; if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) || !ToUint32(cx, val, &column)) { cx->clearPendingException(); column = 0; } reportp = &ownedReport; new (reportp) JSErrorReport(); ownedReport.filename = filename.ptr(); ownedReport.lineno = lineno; ownedReport.exnType = int16_t(JSEXN_NONE); ownedReport.column = column; if (str) { // Note that using |str| for |ucmessage| here is kind of wrong, // because |str| is supposed to be of the format // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to // correspond to |ErrorMessage|. But this is what we've historically // done for duck-typed error objects. // // If only this stuff could get specced one day... if (str->ensureFlat(cx) && strChars.initTwoByte(cx, str)) ownedReport.ucmessage = strChars.twoByteChars(); } } if (str) message_ = bytesStorage.encodeUtf8(cx, str); if (!message_) message_ = "unknown (can't convert to string)"; if (!reportp) { // This is basically an inlined version of // // JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, // JSMSG_UNCAUGHT_EXCEPTION, message_); // // but without the reporting bits. Instead it just puts all // the stuff we care about in our ownedReport and message_. if (!populateUncaughtExceptionReport(cx, message_)) { // Just give up. We're out of memory or something; not much we can // do here. return false; } } else { /* Flag the error as an exception. */ reportp->flags |= JSREPORT_EXCEPTION; } return true; } bool ErrorReport::populateUncaughtExceptionReport(JSContext* cx, ...) { va_list ap; va_start(ap, cx); bool ok = populateUncaughtExceptionReportVA(cx, ap); va_end(ap); return ok; } bool ErrorReport::populateUncaughtExceptionReportVA(JSContext* cx, va_list ap) { new (&ownedReport) JSErrorReport(); ownedReport.flags = JSREPORT_ERROR; ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION; // XXXbz this assumes the stack we have right now is still // related to our exception object. It would be better if we // could accept a passed-in stack of some sort instead. NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); if (!iter.done()) { ownedReport.filename = iter.scriptFilename(); ownedReport.lineno = iter.computeLine(&ownedReport.column); // XXX: Make the column 1-based as in other browsers, instead of 0-based // which is how SpiderMonkey stores it internally. This will be // unnecessary once bug 1144340 is fixed. ++ownedReport.column; ownedReport.isMuted = iter.mutedErrors(); } if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr, JSMSG_UNCAUGHT_EXCEPTION, &ownedMessage, &ownedReport, ArgumentsAreASCII, ap)) { return false; } reportp = &ownedReport; message_ = ownedMessage; ownsMessageAndReport = true; return true; } JSObject* js::CopyErrorObject(JSContext* cx, Handle err) { js::ScopedJSFreePtr copyReport; if (JSErrorReport* errorReport = err->getErrorReport()) { copyReport = CopyErrorReport(cx, errorReport); if (!copyReport) return nullptr; } RootedString message(cx, err->getMessage()); if (message && !cx->compartment()->wrap(cx, &message)) return nullptr; RootedString fileName(cx, err->fileName(cx)); if (!cx->compartment()->wrap(cx, &fileName)) return nullptr; RootedObject stack(cx, err->stack()); if (!cx->compartment()->wrap(cx, &stack)) return nullptr; uint32_t lineNumber = err->lineNumber(); uint32_t columnNumber = err->columnNumber(); JSExnType errorType = err->type(); // Create the Error object. return ErrorObject::create(cx, errorType, stack, fileName, lineNumber, columnNumber, ©Report, message); } JS_PUBLIC_API(bool) JS::CreateError(JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName, uint32_t lineNumber, uint32_t columnNumber, JSErrorReport* report, HandleString message, MutableHandleValue rval) { assertSameCompartment(cx, stack, fileName, message); AssertObjectIsSavedFrameOrWrapper(cx, stack); js::ScopedJSFreePtr rep; if (report) rep = CopyErrorReport(cx, report); RootedObject obj(cx, js::ErrorObject::create(cx, type, stack, fileName, lineNumber, columnNumber, &rep, message)); if (!obj) return false; rval.setObject(*obj); return true; } const char* js::ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes) { if (val.isUndefined()) { return "undefined"; } if (val.isNull()) { return "null"; } RootedString str(cx, JS_ValueToSource(cx, val)); if (!str) { JS_ClearPendingException(cx); return "<>"; } StringBuffer sb(cx); if (val.isObject()) { RootedObject valObj(cx, val.toObjectOrNull()); ESClassValue cls; if (!GetBuiltinClass(cx, valObj, &cls)) { JS_ClearPendingException(cx); return "<>"; } if (cls == ESClass_Array) { sb.append("the array "); } else if (cls == ESClass_ArrayBuffer) { sb.append("the array buffer "); } else if (JS_IsArrayBufferViewObject(valObj)) { sb.append("the typed array "); } else { sb.append("the object "); } } else if (val.isNumber()) { sb.append("the number "); } else if (val.isString()) { sb.append("the string "); } else { MOZ_ASSERT(val.isBoolean() || val.isSymbol()); return bytes.encodeLatin1(cx, str); } sb.append(str); return bytes.encodeLatin1(cx, sb.finishString()); }