michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS standard exception implementation. michael@0: */ michael@0: michael@0: #include "jsexn.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfun.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jsscript.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "gc/Marking.h" michael@0: #include "vm/ErrorObject.h" michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/ErrorObject-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::ArrayLength; michael@0: using mozilla::PodArrayZero; michael@0: using mozilla::PodZero; michael@0: michael@0: static void michael@0: exn_finalize(FreeOp *fop, JSObject *obj); michael@0: michael@0: const Class ErrorObject::class_ = { michael@0: js_Error_str, michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Error) | michael@0: JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: exn_finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr /* construct */ michael@0: }; michael@0: michael@0: JSErrorReport * michael@0: js::CopyErrorReport(JSContext *cx, JSErrorReport *report) michael@0: { michael@0: /* michael@0: * We use a single malloc block to make a deep copy of JSErrorReport with michael@0: * the following layout: michael@0: * JSErrorReport michael@0: * array of copies of report->messageArgs michael@0: * jschar array with characters for all messageArgs michael@0: * jschar array with characters for ucmessage michael@0: * jschar array with characters for uclinebuf and uctokenptr michael@0: * char array with characters for linebuf and tokenptr michael@0: * char array with characters for filename michael@0: * Such layout together with the properties enforced by the following michael@0: * asserts does not need any extra alignment padding. michael@0: */ michael@0: JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char *) == 0); michael@0: JS_STATIC_ASSERT(sizeof(const char *) % sizeof(jschar) == 0); michael@0: michael@0: size_t filenameSize; michael@0: size_t linebufSize; michael@0: size_t uclinebufSize; michael@0: size_t ucmessageSize; michael@0: size_t i, argsArraySize, argsCopySize, argSize; michael@0: size_t mallocSize; michael@0: JSErrorReport *copy; michael@0: uint8_t *cursor; michael@0: michael@0: #define JS_CHARS_SIZE(jschars) ((js_strlen(jschars) + 1) * sizeof(jschar)) michael@0: michael@0: filenameSize = report->filename ? strlen(report->filename) + 1 : 0; michael@0: linebufSize = report->linebuf ? strlen(report->linebuf) + 1 : 0; michael@0: uclinebufSize = report->uclinebuf ? JS_CHARS_SIZE(report->uclinebuf) : 0; michael@0: ucmessageSize = 0; michael@0: argsArraySize = 0; michael@0: argsCopySize = 0; michael@0: if (report->ucmessage) { michael@0: ucmessageSize = JS_CHARS_SIZE(report->ucmessage); michael@0: if (report->messageArgs) { michael@0: for (i = 0; report->messageArgs[i]; ++i) michael@0: argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]); michael@0: michael@0: /* Non-null messageArgs should have at least one non-null arg. */ michael@0: JS_ASSERT(i != 0); michael@0: argsArraySize = (i + 1) * sizeof(const jschar *); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * The mallocSize can not overflow since it represents the sum of the michael@0: * sizes of already allocated objects. michael@0: */ michael@0: mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize + michael@0: ucmessageSize + uclinebufSize + linebufSize + filenameSize; michael@0: cursor = cx->pod_malloc(mallocSize); michael@0: if (!cursor) michael@0: return nullptr; michael@0: michael@0: copy = (JSErrorReport *)cursor; michael@0: memset(cursor, 0, sizeof(JSErrorReport)); michael@0: cursor += sizeof(JSErrorReport); michael@0: michael@0: if (argsArraySize != 0) { michael@0: copy->messageArgs = (const jschar **)cursor; michael@0: cursor += argsArraySize; michael@0: for (i = 0; report->messageArgs[i]; ++i) { michael@0: copy->messageArgs[i] = (const jschar *)cursor; michael@0: argSize = JS_CHARS_SIZE(report->messageArgs[i]); michael@0: js_memcpy(cursor, report->messageArgs[i], argSize); michael@0: cursor += argSize; michael@0: } michael@0: copy->messageArgs[i] = nullptr; michael@0: JS_ASSERT(cursor == (uint8_t *)copy->messageArgs[0] + argsCopySize); michael@0: } michael@0: michael@0: if (report->ucmessage) { michael@0: copy->ucmessage = (const jschar *)cursor; michael@0: js_memcpy(cursor, report->ucmessage, ucmessageSize); michael@0: cursor += ucmessageSize; michael@0: } michael@0: michael@0: if (report->uclinebuf) { michael@0: copy->uclinebuf = (const jschar *)cursor; michael@0: js_memcpy(cursor, report->uclinebuf, uclinebufSize); michael@0: cursor += uclinebufSize; michael@0: if (report->uctokenptr) { michael@0: copy->uctokenptr = copy->uclinebuf + (report->uctokenptr - michael@0: report->uclinebuf); michael@0: } michael@0: } michael@0: michael@0: if (report->linebuf) { michael@0: copy->linebuf = (const char *)cursor; michael@0: js_memcpy(cursor, report->linebuf, linebufSize); michael@0: cursor += linebufSize; michael@0: if (report->tokenptr) { michael@0: copy->tokenptr = copy->linebuf + (report->tokenptr - michael@0: report->linebuf); michael@0: } michael@0: } michael@0: michael@0: if (report->filename) { michael@0: copy->filename = (const char *)cursor; michael@0: js_memcpy(cursor, report->filename, filenameSize); michael@0: } michael@0: JS_ASSERT(cursor + filenameSize == (uint8_t *)copy + mallocSize); michael@0: michael@0: /* HOLD called by the destination error object. */ michael@0: copy->originPrincipals = report->originPrincipals; michael@0: michael@0: /* Copy non-pointer members. */ michael@0: copy->lineno = report->lineno; michael@0: copy->column = report->column; michael@0: copy->errorNumber = report->errorNumber; michael@0: copy->exnType = report->exnType; michael@0: michael@0: /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */ michael@0: copy->flags = report->flags; michael@0: michael@0: #undef JS_CHARS_SIZE michael@0: return copy; michael@0: } michael@0: michael@0: struct SuppressErrorsGuard michael@0: { michael@0: JSContext *cx; michael@0: JSErrorReporter prevReporter; michael@0: JS::AutoSaveExceptionState prevState; michael@0: michael@0: SuppressErrorsGuard(JSContext *cx) michael@0: : cx(cx), michael@0: prevReporter(JS_SetErrorReporter(cx, nullptr)), michael@0: prevState(cx) michael@0: {} michael@0: michael@0: ~SuppressErrorsGuard() michael@0: { michael@0: JS_SetErrorReporter(cx, prevReporter); michael@0: } michael@0: }; michael@0: michael@0: JSString * michael@0: js::ComputeStackString(JSContext *cx) michael@0: { michael@0: StringBuffer sb(cx); michael@0: michael@0: { michael@0: RootedAtom atom(cx); michael@0: SuppressErrorsGuard seg(cx); michael@0: for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED, michael@0: cx->compartment()->principals); michael@0: !i.done(); michael@0: ++i) michael@0: { michael@0: /* First append the function name, if any. */ michael@0: if (i.isNonEvalFunctionFrame()) michael@0: atom = i.functionDisplayAtom(); michael@0: else michael@0: atom = nullptr; michael@0: if (atom && !sb.append(atom)) michael@0: return nullptr; michael@0: michael@0: /* Next a @ separating function name from source location. */ michael@0: if (!sb.append('@')) michael@0: return nullptr; michael@0: michael@0: /* Now the filename. */ michael@0: const char *cfilename = i.scriptFilename(); michael@0: if (!cfilename) michael@0: cfilename = ""; michael@0: if (!sb.appendInflated(cfilename, strlen(cfilename))) michael@0: return nullptr; michael@0: michael@0: uint32_t column = 0; michael@0: uint32_t line = i.computeLine(&column); michael@0: // Now the line number michael@0: if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb)) michael@0: return nullptr; michael@0: michael@0: // Finally, : followed by the column number (1-based, as in other browsers) michael@0: // and a newline. michael@0: if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(column + 1), sb) || michael@0: !sb.append('\n')) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Cut off the stack if it gets too deep (most commonly for michael@0: * infinite recursion errors). michael@0: */ michael@0: const size_t MaxReportedStackDepth = 1u << 20; michael@0: if (sb.length() > MaxReportedStackDepth) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return sb.finishString(); michael@0: } michael@0: michael@0: static void michael@0: exn_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: if (JSErrorReport *report = obj->as().getErrorReport()) { michael@0: /* These were held by ErrorObject::init. */ michael@0: if (JSPrincipals *prin = report->originPrincipals) michael@0: JS_DropPrincipals(fop->runtime(), prin); michael@0: fop->free_(report); michael@0: } michael@0: } michael@0: michael@0: JSErrorReport * michael@0: js_ErrorFromException(JSContext *cx, HandleObject objArg) michael@0: { michael@0: // It's ok to UncheckedUnwrap here, since all we do is get the michael@0: // JSErrorReport, and consumers are careful with the information they get michael@0: // from that anyway. Anyone doing things that would expose anything in the michael@0: // JSErrorReport to page script either does a security check on the michael@0: // JSErrorReport's principal or also tries to do toString on our object and michael@0: // will fail if they can't unwrap it. michael@0: RootedObject obj(cx, UncheckedUnwrap(objArg)); michael@0: if (!obj->is()) michael@0: return nullptr; michael@0: michael@0: return obj->as().getOrCreateErrorReport(cx); michael@0: } michael@0: michael@0: static bool michael@0: Error(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Compute the error message, if any. */ michael@0: RootedString message(cx, nullptr); michael@0: if (args.hasDefined(0)) { michael@0: message = ToString(cx, args[0]); michael@0: if (!message) michael@0: return false; michael@0: } michael@0: michael@0: /* Find the scripted caller. */ michael@0: NonBuiltinFrameIter iter(cx); michael@0: michael@0: /* Set the 'fileName' property. */ michael@0: RootedString fileName(cx); michael@0: if (args.length() > 1) { michael@0: fileName = ToString(cx, args[1]); michael@0: } else { michael@0: fileName = cx->runtime()->emptyString; michael@0: if (!iter.done()) { michael@0: if (const char *cfilename = iter.scriptFilename()) michael@0: fileName = JS_NewStringCopyZ(cx, cfilename); michael@0: } michael@0: } michael@0: if (!fileName) michael@0: return false; michael@0: michael@0: /* Set the 'lineNumber' property. */ michael@0: uint32_t lineNumber, columnNumber = 0; michael@0: if (args.length() > 2) { michael@0: if (!ToUint32(cx, args[2], &lineNumber)) michael@0: return false; michael@0: } else { michael@0: lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber); michael@0: } michael@0: michael@0: Rooted stack(cx, ComputeStackString(cx)); michael@0: if (!stack) michael@0: return false; michael@0: michael@0: /* michael@0: * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when michael@0: * called as functions, without operator new. But as we do not give michael@0: * each constructor a distinct JSClass, we must get the exception type michael@0: * ourselves. michael@0: */ michael@0: JSExnType exnType = JSExnType(args.callee().as().getExtendedSlot(0).toInt32()); michael@0: michael@0: RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName, michael@0: lineNumber, columnNumber, nullptr, message)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.11.4.4 (NB: with subsequent errata). */ michael@0: static bool michael@0: exn_toString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 2. */ michael@0: if (!args.thisv().isObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "Error"); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 1. */ michael@0: RootedObject obj(cx, &args.thisv().toObject()); michael@0: michael@0: /* Step 3. */ michael@0: RootedValue nameVal(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: RootedString name(cx); michael@0: if (nameVal.isUndefined()) { michael@0: name = cx->names().Error; michael@0: } else { michael@0: name = ToString(cx, nameVal); michael@0: if (!name) michael@0: return false; michael@0: } michael@0: michael@0: /* Step 5. */ michael@0: RootedValue msgVal(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &msgVal)) michael@0: return false; michael@0: michael@0: /* Step 6. */ michael@0: RootedString message(cx); michael@0: if (msgVal.isUndefined()) { michael@0: message = cx->runtime()->emptyString; michael@0: } else { michael@0: message = ToString(cx, msgVal); michael@0: if (!message) michael@0: return false; michael@0: } michael@0: michael@0: /* Step 7. */ michael@0: if (name->empty() && message->empty()) { michael@0: args.rval().setString(cx->names().Error); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 8. */ michael@0: if (name->empty()) { michael@0: args.rval().setString(message); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 9. */ michael@0: if (message->empty()) { michael@0: args.rval().setString(name); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 10. */ michael@0: StringBuffer sb(cx); michael@0: if (!sb.append(name) || !sb.append(": ") || !sb.append(message)) michael@0: return false; michael@0: michael@0: JSString *str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: /* michael@0: * Return a string that may eval to something similar to the original object. michael@0: */ michael@0: static bool michael@0: exn_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: RootedValue nameVal(cx); michael@0: RootedString name(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal) || michael@0: !(name = ToString(cx, nameVal))) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: RootedValue messageVal(cx); michael@0: RootedString message(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &messageVal) || michael@0: !(message = ValueToSource(cx, messageVal))) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: RootedValue filenameVal(cx); michael@0: RootedString filename(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().fileName, &filenameVal) || michael@0: !(filename = ValueToSource(cx, filenameVal))) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: RootedValue linenoVal(cx); michael@0: uint32_t lineno; michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) || michael@0: !ToUint32(cx, linenoVal, &lineno)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!sb.append("(new ") || !sb.append(name) || !sb.append("(")) michael@0: return false; michael@0: michael@0: if (!sb.append(message)) michael@0: return false; michael@0: michael@0: if (!filename->empty()) { michael@0: if (!sb.append(", ") || !sb.append(filename)) michael@0: return false; michael@0: } michael@0: if (lineno != 0) { michael@0: /* We have a line, but no filename, add empty string */ michael@0: if (filename->empty() && !sb.append(", \"\"")) michael@0: return false; michael@0: michael@0: JSString *linenumber = ToString(cx, linenoVal); michael@0: if (!linenumber) michael@0: return false; michael@0: if (!sb.append(", ") || !sb.append(linenumber)) michael@0: return false; michael@0: } michael@0: michael@0: if (!sb.append("))")) michael@0: return false; michael@0: michael@0: JSString *str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec exception_methods[] = { michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, exn_toSource, 0,0), michael@0: #endif michael@0: JS_FN(js_toString_str, exn_toString, 0,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: /* JSProto_ ordering for exceptions shall match JSEXN_ constants. */ michael@0: JS_STATIC_ASSERT(JSEXN_ERR == 0); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_INTERNALERR == JSProto_InternalError); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_EVALERR == JSProto_EvalError); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_RANGEERR == JSProto_RangeError); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_SYNTAXERR == JSProto_SyntaxError); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_TYPEERR == JSProto_TypeError); michael@0: JS_STATIC_ASSERT(JSProto_Error + JSEXN_URIERR == JSProto_URIError); michael@0: michael@0: /* static */ ErrorObject * michael@0: ErrorObject::createProto(JSContext *cx, JS::Handle global, JSExnType type, michael@0: JS::HandleObject proto) michael@0: { michael@0: RootedObject errorProto(cx); michael@0: errorProto = global->createBlankPrototypeInheriting(cx, &ErrorObject::class_, *proto); michael@0: if (!errorProto) michael@0: return nullptr; michael@0: michael@0: Rooted err(cx, &errorProto->as()); michael@0: RootedString emptyStr(cx, cx->names().empty); michael@0: if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr)) michael@0: return nullptr; michael@0: michael@0: // The various prototypes also have .name in addition to the normal error michael@0: // instance properties. michael@0: JSProtoKey key = GetExceptionProtoKey(type); michael@0: RootedPropertyName name(cx, ClassName(key, cx)); michael@0: RootedValue nameValue(cx, StringValue(name)); michael@0: if (!JSObject::defineProperty(cx, err, cx->names().name, nameValue, michael@0: JS_PropertyStub, JS_StrictPropertyStub, 0)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Create the corresponding constructor. michael@0: RootedFunction ctor(cx, global->createConstructor(cx, Error, name, 1, michael@0: JSFunction::ExtendedFinalizeKind)); michael@0: if (!ctor) michael@0: return nullptr; michael@0: ctor->setExtendedSlot(0, Int32Value(int32_t(type))); michael@0: michael@0: if (!LinkConstructorAndPrototype(cx, ctor, err)) michael@0: return nullptr; michael@0: michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, err)) michael@0: return nullptr; michael@0: michael@0: return err; michael@0: } michael@0: michael@0: JSObject * michael@0: js_InitExceptionClasses(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: Rooted global(cx, &obj->as()); michael@0: michael@0: RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx)); michael@0: if (!objProto) michael@0: return nullptr; michael@0: michael@0: /* Initialize the base Error class first. */ michael@0: RootedObject errorProto(cx, ErrorObject::createProto(cx, global, JSEXN_ERR, objProto)); michael@0: if (!errorProto) michael@0: return nullptr; michael@0: michael@0: /* |Error.prototype| alone has method properties. */ michael@0: if (!DefinePropertiesAndBrand(cx, errorProto, nullptr, exception_methods)) michael@0: return nullptr; michael@0: michael@0: /* Define all remaining *Error constructors. */ michael@0: for (int i = JSEXN_ERR + 1; i < JSEXN_LIMIT; i++) { michael@0: if (!ErrorObject::createProto(cx, global, JSExnType(i), errorProto)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return errorProto; michael@0: } michael@0: michael@0: const JSErrorFormatString* michael@0: js_GetLocalizedErrorMessage(ExclusiveContext *cx, void *userRef, const char *locale, michael@0: const unsigned errorNumber) michael@0: { michael@0: const JSErrorFormatString *errorString = nullptr; michael@0: michael@0: // The locale callbacks might not be thread safe, so don't call them if michael@0: // we're not on the main thread. When used with XPConnect, michael@0: // |localeGetErrorMessage| will be nullptr anyways. michael@0: if (cx->isJSContext() && michael@0: cx->asJSContext()->runtime()->localeCallbacks && michael@0: cx->asJSContext()->runtime()->localeCallbacks->localeGetErrorMessage) michael@0: { michael@0: JSLocaleCallbacks *callbacks = cx->asJSContext()->runtime()->localeCallbacks; michael@0: errorString = callbacks->localeGetErrorMessage(userRef, locale, errorNumber); michael@0: } michael@0: michael@0: if (!errorString) michael@0: errorString = js_GetErrorMessage(userRef, locale, errorNumber); michael@0: return errorString; michael@0: } michael@0: michael@0: JS_FRIEND_API(const jschar*) michael@0: js::GetErrorTypeName(JSRuntime* rt, int16_t exnType) michael@0: { michael@0: /* michael@0: * JSEXN_INTERNALERR returns null to prevent that "InternalError: " michael@0: * is prepended before "uncaught exception: " michael@0: */ michael@0: if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT || michael@0: exnType == JSEXN_INTERNALERR) michael@0: { michael@0: return nullptr; michael@0: } michael@0: JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType)); michael@0: return ClassName(key, rt)->chars(); michael@0: } michael@0: michael@0: bool michael@0: js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp, michael@0: JSErrorCallback callback, void *userRef) michael@0: { michael@0: // Tell our caller to report immediately if this report is just a warning. michael@0: JS_ASSERT(reportp); michael@0: if (JSREPORT_IS_WARNING(reportp->flags)) michael@0: return false; michael@0: michael@0: // Find the exception index associated with this error. michael@0: JSErrNum errorNumber = static_cast(reportp->errorNumber); michael@0: const JSErrorFormatString *errorString; michael@0: if (!callback || callback == js_GetErrorMessage) michael@0: errorString = js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber); michael@0: else michael@0: errorString = callback(userRef, nullptr, errorNumber); michael@0: JSExnType exnType = errorString ? static_cast(errorString->exnType) : JSEXN_NONE; michael@0: MOZ_ASSERT(exnType < JSEXN_LIMIT); michael@0: michael@0: // Return false (no exception raised) if no exception is associated michael@0: // with the given error number. michael@0: if (exnType == JSEXN_NONE) michael@0: return false; michael@0: michael@0: // Prevent infinite recursion. michael@0: if (cx->generatingError) michael@0: return false; michael@0: AutoScopedAssign asa(&cx->generatingError, true); michael@0: michael@0: // Create an exception object. michael@0: RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage) michael@0: : JS_NewStringCopyZ(cx, message)); michael@0: if (!messageStr) michael@0: return cx->isExceptionPending(); michael@0: michael@0: RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename)); michael@0: if (!fileName) michael@0: return cx->isExceptionPending(); michael@0: michael@0: uint32_t lineNumber = reportp->lineno; michael@0: uint32_t columnNumber = reportp->column; michael@0: michael@0: RootedString stack(cx, ComputeStackString(cx)); michael@0: if (!stack) michael@0: return cx->isExceptionPending(); michael@0: michael@0: js::ScopedJSFreePtr report(CopyErrorReport(cx, reportp)); michael@0: if (!report) michael@0: return cx->isExceptionPending(); michael@0: michael@0: RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName, michael@0: lineNumber, columnNumber, &report, messageStr)); michael@0: if (!errObject) michael@0: return cx->isExceptionPending(); michael@0: michael@0: // Throw it. michael@0: RootedValue errValue(cx, ObjectValue(*errObject)); michael@0: JS_SetPendingException(cx, errValue); michael@0: michael@0: // Flag the error report passed in to indicate an exception was raised. michael@0: reportp->flags |= JSREPORT_EXCEPTION; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsDuckTypedErrorObject(JSContext *cx, HandleObject exnObject, const char **filename_strp) michael@0: { michael@0: bool found; michael@0: if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found) michael@0: return false; michael@0: michael@0: const char *filename_str = *filename_strp; michael@0: if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) { michael@0: /* DOMException duck quacks "filename" (all lowercase) */ michael@0: filename_str = "filename"; michael@0: if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) michael@0: return false; michael@0: } michael@0: michael@0: if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found) michael@0: return false; michael@0: michael@0: *filename_strp = filename_str; michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(JSString *) michael@0: js::ErrorReportToString(JSContext *cx, JSErrorReport *reportp) michael@0: { michael@0: JSExnType type = static_cast(reportp->exnType); michael@0: RootedString str(cx, cx->runtime()->emptyString); michael@0: if (type != JSEXN_NONE) michael@0: str = ClassName(GetExceptionProtoKey(type), cx); michael@0: RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2)); michael@0: if (!str || !toAppend) michael@0: return nullptr; michael@0: str = ConcatStrings(cx, str, toAppend); michael@0: if (!str) michael@0: return nullptr; michael@0: toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage); michael@0: if (toAppend) michael@0: str = ConcatStrings(cx, str, toAppend); michael@0: return str; michael@0: } michael@0: michael@0: bool michael@0: js_ReportUncaughtException(JSContext *cx) michael@0: { michael@0: if (!cx->isExceptionPending()) michael@0: return true; michael@0: michael@0: RootedValue exn(cx); michael@0: if (!cx->getPendingException(&exn)) michael@0: return false; michael@0: michael@0: /* michael@0: * Because ToString below could error and an exception object could become michael@0: * unrooted, we must root exnObject. Later, if exnObject is non-null, we michael@0: * need to root other intermediates, so allocate an operand stack segment michael@0: * to protect all of these values. michael@0: */ michael@0: RootedObject exnObject(cx); michael@0: if (JSVAL_IS_PRIMITIVE(exn)) { michael@0: exnObject = nullptr; michael@0: } else { michael@0: exnObject = JSVAL_TO_OBJECT(exn); michael@0: } michael@0: michael@0: JS_ClearPendingException(cx); michael@0: JSErrorReport *reportp = exnObject ? js_ErrorFromException(cx, exnObject) michael@0: : nullptr; michael@0: michael@0: // Be careful not to invoke ToString if we've already successfully extracted michael@0: // an error report, since the exception might be wrapped in a security michael@0: // wrapper, and ToString-ing it might throw. michael@0: RootedString str(cx); michael@0: if (reportp) michael@0: str = ErrorReportToString(cx, reportp); michael@0: else michael@0: str = ToString(cx, exn); michael@0: michael@0: JSErrorReport report; michael@0: michael@0: // If js_ErrorFromException didn't get us a JSErrorReport, then the object michael@0: // was not an ErrorObject, security-wrapped or otherwise. However, it might michael@0: // still quack like one. Give duck-typing a chance. michael@0: const char *filename_str = js_fileName_str; michael@0: JSAutoByteString filename; michael@0: if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str)) michael@0: { michael@0: // Temporary value for pulling properties off of duck-typed objects. michael@0: RootedValue val(cx); michael@0: michael@0: RootedString name(cx); michael@0: if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString()) michael@0: name = val.toString(); michael@0: michael@0: RootedString msg(cx); michael@0: if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString()) michael@0: msg = val.toString(); michael@0: michael@0: // If we have the right fields, override the ToString we performed on michael@0: // the exception object above with something built out of its quacks michael@0: // (i.e. as much of |NameQuack: MessageQuack| as we can make). michael@0: // michael@0: // It would be nice to use ErrorReportToString here, but we can't quite michael@0: // do it - mostly because we'd need to figure out what JSExnType |name| michael@0: // corresponds to, which may not be any JSExnType at all. michael@0: if (name && msg) { michael@0: RootedString colon(cx, JS_NewStringCopyZ(cx, ": ")); michael@0: if (!colon) michael@0: return false; michael@0: RootedString nameColon(cx, ConcatStrings(cx, name, colon)); michael@0: if (!nameColon) michael@0: return false; michael@0: str = ConcatStrings(cx, nameColon, msg); michael@0: if (!str) michael@0: return false; michael@0: } else if (name) { michael@0: str = name; michael@0: } else if (msg) { michael@0: str = msg; michael@0: } michael@0: michael@0: if (JS_GetProperty(cx, exnObject, filename_str, &val)) { michael@0: JSString *tmp = ToString(cx, val); michael@0: if (tmp) michael@0: filename.encodeLatin1(cx, tmp); michael@0: } michael@0: michael@0: uint32_t lineno; michael@0: if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) || michael@0: !ToUint32(cx, val, &lineno)) michael@0: { michael@0: lineno = 0; michael@0: } michael@0: michael@0: uint32_t column; michael@0: if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) || michael@0: !ToUint32(cx, val, &column)) michael@0: { michael@0: column = 0; michael@0: } michael@0: michael@0: reportp = &report; michael@0: PodZero(&report); michael@0: report.filename = filename.ptr(); michael@0: report.lineno = (unsigned) lineno; michael@0: report.exnType = int16_t(JSEXN_NONE); michael@0: report.column = (unsigned) column; michael@0: if (str) { michael@0: // Note that using |str| for |ucmessage| here is kind of wrong, michael@0: // because |str| is supposed to be of the format michael@0: // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to michael@0: // correspond to |ErrorMessage|. But this is what we've historically michael@0: // done for duck-typed error objects. michael@0: // michael@0: // If only this stuff could get specced one day... michael@0: if (JSFlatString *flat = str->ensureFlat(cx)) michael@0: report.ucmessage = flat->chars(); michael@0: } michael@0: } michael@0: michael@0: JSAutoByteString bytesStorage; michael@0: const char *bytes = nullptr; michael@0: if (str) michael@0: bytes = bytesStorage.encodeLatin1(cx, str); michael@0: if (!bytes) michael@0: bytes = "unknown (can't convert to string)"; michael@0: michael@0: if (!reportp) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_UNCAUGHT_EXCEPTION, bytes); michael@0: } else { michael@0: /* Flag the error as an exception. */ michael@0: reportp->flags |= JSREPORT_EXCEPTION; michael@0: michael@0: /* Pass the exception object. */ michael@0: JS_SetPendingException(cx, exn); michael@0: CallErrorReporter(cx, bytes, reportp); michael@0: } michael@0: michael@0: JS_ClearPendingException(cx); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js_CopyErrorObject(JSContext *cx, Handle err, HandleObject scope) michael@0: { michael@0: assertSameCompartment(cx, scope); michael@0: michael@0: js::ScopedJSFreePtr copyReport; michael@0: if (JSErrorReport *errorReport = err->getErrorReport()) { michael@0: copyReport = CopyErrorReport(cx, errorReport); michael@0: if (!copyReport) michael@0: return nullptr; michael@0: } michael@0: michael@0: RootedString message(cx, err->getMessage()); michael@0: if (message && !cx->compartment()->wrap(cx, message.address())) michael@0: return nullptr; michael@0: RootedString fileName(cx, err->fileName(cx)); michael@0: if (!cx->compartment()->wrap(cx, fileName.address())) michael@0: return nullptr; michael@0: RootedString stack(cx, err->stack(cx)); michael@0: if (!cx->compartment()->wrap(cx, stack.address())) michael@0: return nullptr; michael@0: uint32_t lineNumber = err->lineNumber(); michael@0: uint32_t columnNumber = err->columnNumber(); michael@0: JSExnType errorType = err->type(); michael@0: michael@0: // Create the Error object. michael@0: return ErrorObject::create(cx, errorType, stack, fileName, michael@0: lineNumber, columnNumber, ©Report, message); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS::CreateTypeError(JSContext *cx, HandleString stack, HandleString fileName, michael@0: uint32_t lineNumber, uint32_t columnNumber, JSErrorReport *report, michael@0: HandleString message, MutableHandleValue rval) michael@0: { michael@0: assertSameCompartment(cx, stack, fileName, message); michael@0: js::ScopedJSFreePtr rep; michael@0: if (report) michael@0: rep = CopyErrorReport(cx, report); michael@0: michael@0: RootedObject obj(cx, michael@0: js::ErrorObject::create(cx, JSEXN_TYPEERR, stack, fileName, michael@0: lineNumber, columnNumber, &rep, message)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: rval.setObject(*obj); michael@0: return true; michael@0: }