js/src/jsexn.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/jsexn.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,930 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +/*
    1.11 + * JS standard exception implementation.
    1.12 + */
    1.13 +
    1.14 +#include "jsexn.h"
    1.15 +
    1.16 +#include "mozilla/ArrayUtils.h"
    1.17 +#include "mozilla/PodOperations.h"
    1.18 +
    1.19 +#include <string.h>
    1.20 +
    1.21 +#include "jsapi.h"
    1.22 +#include "jscntxt.h"
    1.23 +#include "jsfun.h"
    1.24 +#include "jsnum.h"
    1.25 +#include "jsobj.h"
    1.26 +#include "jsscript.h"
    1.27 +#include "jstypes.h"
    1.28 +#include "jsutil.h"
    1.29 +#include "jswrapper.h"
    1.30 +
    1.31 +#include "gc/Marking.h"
    1.32 +#include "vm/ErrorObject.h"
    1.33 +#include "vm/GlobalObject.h"
    1.34 +#include "vm/StringBuffer.h"
    1.35 +
    1.36 +#include "jsobjinlines.h"
    1.37 +
    1.38 +#include "vm/ErrorObject-inl.h"
    1.39 +
    1.40 +using namespace js;
    1.41 +using namespace js::gc;
    1.42 +using namespace js::types;
    1.43 +
    1.44 +using mozilla::ArrayLength;
    1.45 +using mozilla::PodArrayZero;
    1.46 +using mozilla::PodZero;
    1.47 +
    1.48 +static void
    1.49 +exn_finalize(FreeOp *fop, JSObject *obj);
    1.50 +
    1.51 +const Class ErrorObject::class_ = {
    1.52 +    js_Error_str,
    1.53 +    JSCLASS_IMPLEMENTS_BARRIERS |
    1.54 +    JSCLASS_HAS_CACHED_PROTO(JSProto_Error) |
    1.55 +    JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS),
    1.56 +    JS_PropertyStub,         /* addProperty */
    1.57 +    JS_DeletePropertyStub,   /* delProperty */
    1.58 +    JS_PropertyStub,         /* getProperty */
    1.59 +    JS_StrictPropertyStub,   /* setProperty */
    1.60 +    JS_EnumerateStub,
    1.61 +    JS_ResolveStub,
    1.62 +    JS_ConvertStub,
    1.63 +    exn_finalize,
    1.64 +    nullptr,                 /* call        */
    1.65 +    nullptr,                 /* hasInstance */
    1.66 +    nullptr                  /* construct   */
    1.67 +};
    1.68 +
    1.69 +JSErrorReport *
    1.70 +js::CopyErrorReport(JSContext *cx, JSErrorReport *report)
    1.71 +{
    1.72 +    /*
    1.73 +     * We use a single malloc block to make a deep copy of JSErrorReport with
    1.74 +     * the following layout:
    1.75 +     *   JSErrorReport
    1.76 +     *   array of copies of report->messageArgs
    1.77 +     *   jschar array with characters for all messageArgs
    1.78 +     *   jschar array with characters for ucmessage
    1.79 +     *   jschar array with characters for uclinebuf and uctokenptr
    1.80 +     *   char array with characters for linebuf and tokenptr
    1.81 +     *   char array with characters for filename
    1.82 +     * Such layout together with the properties enforced by the following
    1.83 +     * asserts does not need any extra alignment padding.
    1.84 +     */
    1.85 +    JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char *) == 0);
    1.86 +    JS_STATIC_ASSERT(sizeof(const char *) % sizeof(jschar) == 0);
    1.87 +
    1.88 +    size_t filenameSize;
    1.89 +    size_t linebufSize;
    1.90 +    size_t uclinebufSize;
    1.91 +    size_t ucmessageSize;
    1.92 +    size_t i, argsArraySize, argsCopySize, argSize;
    1.93 +    size_t mallocSize;
    1.94 +    JSErrorReport *copy;
    1.95 +    uint8_t *cursor;
    1.96 +
    1.97 +#define JS_CHARS_SIZE(jschars) ((js_strlen(jschars) + 1) * sizeof(jschar))
    1.98 +
    1.99 +    filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
   1.100 +    linebufSize = report->linebuf ? strlen(report->linebuf) + 1 : 0;
   1.101 +    uclinebufSize = report->uclinebuf ? JS_CHARS_SIZE(report->uclinebuf) : 0;
   1.102 +    ucmessageSize = 0;
   1.103 +    argsArraySize = 0;
   1.104 +    argsCopySize = 0;
   1.105 +    if (report->ucmessage) {
   1.106 +        ucmessageSize = JS_CHARS_SIZE(report->ucmessage);
   1.107 +        if (report->messageArgs) {
   1.108 +            for (i = 0; report->messageArgs[i]; ++i)
   1.109 +                argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]);
   1.110 +
   1.111 +            /* Non-null messageArgs should have at least one non-null arg. */
   1.112 +            JS_ASSERT(i != 0);
   1.113 +            argsArraySize = (i + 1) * sizeof(const jschar *);
   1.114 +        }
   1.115 +    }
   1.116 +
   1.117 +    /*
   1.118 +     * The mallocSize can not overflow since it represents the sum of the
   1.119 +     * sizes of already allocated objects.
   1.120 +     */
   1.121 +    mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize +
   1.122 +                 ucmessageSize + uclinebufSize + linebufSize + filenameSize;
   1.123 +    cursor = cx->pod_malloc<uint8_t>(mallocSize);
   1.124 +    if (!cursor)
   1.125 +        return nullptr;
   1.126 +
   1.127 +    copy = (JSErrorReport *)cursor;
   1.128 +    memset(cursor, 0, sizeof(JSErrorReport));
   1.129 +    cursor += sizeof(JSErrorReport);
   1.130 +
   1.131 +    if (argsArraySize != 0) {
   1.132 +        copy->messageArgs = (const jschar **)cursor;
   1.133 +        cursor += argsArraySize;
   1.134 +        for (i = 0; report->messageArgs[i]; ++i) {
   1.135 +            copy->messageArgs[i] = (const jschar *)cursor;
   1.136 +            argSize = JS_CHARS_SIZE(report->messageArgs[i]);
   1.137 +            js_memcpy(cursor, report->messageArgs[i], argSize);
   1.138 +            cursor += argSize;
   1.139 +        }
   1.140 +        copy->messageArgs[i] = nullptr;
   1.141 +        JS_ASSERT(cursor == (uint8_t *)copy->messageArgs[0] + argsCopySize);
   1.142 +    }
   1.143 +
   1.144 +    if (report->ucmessage) {
   1.145 +        copy->ucmessage = (const jschar *)cursor;
   1.146 +        js_memcpy(cursor, report->ucmessage, ucmessageSize);
   1.147 +        cursor += ucmessageSize;
   1.148 +    }
   1.149 +
   1.150 +    if (report->uclinebuf) {
   1.151 +        copy->uclinebuf = (const jschar *)cursor;
   1.152 +        js_memcpy(cursor, report->uclinebuf, uclinebufSize);
   1.153 +        cursor += uclinebufSize;
   1.154 +        if (report->uctokenptr) {
   1.155 +            copy->uctokenptr = copy->uclinebuf + (report->uctokenptr -
   1.156 +                                                  report->uclinebuf);
   1.157 +        }
   1.158 +    }
   1.159 +
   1.160 +    if (report->linebuf) {
   1.161 +        copy->linebuf = (const char *)cursor;
   1.162 +        js_memcpy(cursor, report->linebuf, linebufSize);
   1.163 +        cursor += linebufSize;
   1.164 +        if (report->tokenptr) {
   1.165 +            copy->tokenptr = copy->linebuf + (report->tokenptr -
   1.166 +                                              report->linebuf);
   1.167 +        }
   1.168 +    }
   1.169 +
   1.170 +    if (report->filename) {
   1.171 +        copy->filename = (const char *)cursor;
   1.172 +        js_memcpy(cursor, report->filename, filenameSize);
   1.173 +    }
   1.174 +    JS_ASSERT(cursor + filenameSize == (uint8_t *)copy + mallocSize);
   1.175 +
   1.176 +    /* HOLD called by the destination error object. */
   1.177 +    copy->originPrincipals = report->originPrincipals;
   1.178 +
   1.179 +    /* Copy non-pointer members. */
   1.180 +    copy->lineno = report->lineno;
   1.181 +    copy->column = report->column;
   1.182 +    copy->errorNumber = report->errorNumber;
   1.183 +    copy->exnType = report->exnType;
   1.184 +
   1.185 +    /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
   1.186 +    copy->flags = report->flags;
   1.187 +
   1.188 +#undef JS_CHARS_SIZE
   1.189 +    return copy;
   1.190 +}
   1.191 +
   1.192 +struct SuppressErrorsGuard
   1.193 +{
   1.194 +    JSContext *cx;
   1.195 +    JSErrorReporter prevReporter;
   1.196 +    JS::AutoSaveExceptionState prevState;
   1.197 +
   1.198 +    SuppressErrorsGuard(JSContext *cx)
   1.199 +      : cx(cx),
   1.200 +        prevReporter(JS_SetErrorReporter(cx, nullptr)),
   1.201 +        prevState(cx)
   1.202 +    {}
   1.203 +
   1.204 +    ~SuppressErrorsGuard()
   1.205 +    {
   1.206 +        JS_SetErrorReporter(cx, prevReporter);
   1.207 +    }
   1.208 +};
   1.209 +
   1.210 +JSString *
   1.211 +js::ComputeStackString(JSContext *cx)
   1.212 +{
   1.213 +    StringBuffer sb(cx);
   1.214 +
   1.215 +    {
   1.216 +        RootedAtom atom(cx);
   1.217 +        SuppressErrorsGuard seg(cx);
   1.218 +        for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED,
   1.219 +                                   cx->compartment()->principals);
   1.220 +             !i.done();
   1.221 +             ++i)
   1.222 +        {
   1.223 +            /* First append the function name, if any. */
   1.224 +            if (i.isNonEvalFunctionFrame())
   1.225 +                atom = i.functionDisplayAtom();
   1.226 +            else
   1.227 +                atom = nullptr;
   1.228 +            if (atom && !sb.append(atom))
   1.229 +                return nullptr;
   1.230 +
   1.231 +            /* Next a @ separating function name from source location. */
   1.232 +            if (!sb.append('@'))
   1.233 +                return nullptr;
   1.234 +
   1.235 +            /* Now the filename. */
   1.236 +            const char *cfilename = i.scriptFilename();
   1.237 +            if (!cfilename)
   1.238 +                cfilename = "";
   1.239 +            if (!sb.appendInflated(cfilename, strlen(cfilename)))
   1.240 +                return nullptr;
   1.241 +
   1.242 +            uint32_t column = 0;
   1.243 +            uint32_t line = i.computeLine(&column);
   1.244 +            // Now the line number
   1.245 +            if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb))
   1.246 +                return nullptr;
   1.247 +
   1.248 +            // Finally, : followed by the column number (1-based, as in other browsers)
   1.249 +            // and a newline.
   1.250 +            if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(column + 1), sb) ||
   1.251 +                !sb.append('\n'))
   1.252 +            {
   1.253 +                return nullptr;
   1.254 +            }
   1.255 +
   1.256 +            /*
   1.257 +             * Cut off the stack if it gets too deep (most commonly for
   1.258 +             * infinite recursion errors).
   1.259 +             */
   1.260 +            const size_t MaxReportedStackDepth = 1u << 20;
   1.261 +            if (sb.length() > MaxReportedStackDepth)
   1.262 +                break;
   1.263 +        }
   1.264 +    }
   1.265 +
   1.266 +    return sb.finishString();
   1.267 +}
   1.268 +
   1.269 +static void
   1.270 +exn_finalize(FreeOp *fop, JSObject *obj)
   1.271 +{
   1.272 +    if (JSErrorReport *report = obj->as<ErrorObject>().getErrorReport()) {
   1.273 +        /* These were held by ErrorObject::init. */
   1.274 +        if (JSPrincipals *prin = report->originPrincipals)
   1.275 +            JS_DropPrincipals(fop->runtime(), prin);
   1.276 +        fop->free_(report);
   1.277 +    }
   1.278 +}
   1.279 +
   1.280 +JSErrorReport *
   1.281 +js_ErrorFromException(JSContext *cx, HandleObject objArg)
   1.282 +{
   1.283 +    // It's ok to UncheckedUnwrap here, since all we do is get the
   1.284 +    // JSErrorReport, and consumers are careful with the information they get
   1.285 +    // from that anyway.  Anyone doing things that would expose anything in the
   1.286 +    // JSErrorReport to page script either does a security check on the
   1.287 +    // JSErrorReport's principal or also tries to do toString on our object and
   1.288 +    // will fail if they can't unwrap it.
   1.289 +    RootedObject obj(cx, UncheckedUnwrap(objArg));
   1.290 +    if (!obj->is<ErrorObject>())
   1.291 +        return nullptr;
   1.292 +
   1.293 +    return obj->as<ErrorObject>().getOrCreateErrorReport(cx);
   1.294 +}
   1.295 +
   1.296 +static bool
   1.297 +Error(JSContext *cx, unsigned argc, Value *vp)
   1.298 +{
   1.299 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.300 +
   1.301 +    /* Compute the error message, if any. */
   1.302 +    RootedString message(cx, nullptr);
   1.303 +    if (args.hasDefined(0)) {
   1.304 +        message = ToString<CanGC>(cx, args[0]);
   1.305 +        if (!message)
   1.306 +            return false;
   1.307 +    }
   1.308 +
   1.309 +    /* Find the scripted caller. */
   1.310 +    NonBuiltinFrameIter iter(cx);
   1.311 +
   1.312 +    /* Set the 'fileName' property. */
   1.313 +    RootedString fileName(cx);
   1.314 +    if (args.length() > 1) {
   1.315 +        fileName = ToString<CanGC>(cx, args[1]);
   1.316 +    } else {
   1.317 +        fileName = cx->runtime()->emptyString;
   1.318 +        if (!iter.done()) {
   1.319 +            if (const char *cfilename = iter.scriptFilename())
   1.320 +                fileName = JS_NewStringCopyZ(cx, cfilename);
   1.321 +        }
   1.322 +    }
   1.323 +    if (!fileName)
   1.324 +        return false;
   1.325 +
   1.326 +    /* Set the 'lineNumber' property. */
   1.327 +    uint32_t lineNumber, columnNumber = 0;
   1.328 +    if (args.length() > 2) {
   1.329 +        if (!ToUint32(cx, args[2], &lineNumber))
   1.330 +            return false;
   1.331 +    } else {
   1.332 +        lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
   1.333 +    }
   1.334 +
   1.335 +    Rooted<JSString*> stack(cx, ComputeStackString(cx));
   1.336 +    if (!stack)
   1.337 +        return false;
   1.338 +
   1.339 +    /*
   1.340 +     * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
   1.341 +     * called as functions, without operator new.  But as we do not give
   1.342 +     * each constructor a distinct JSClass, we must get the exception type
   1.343 +     * ourselves.
   1.344 +     */
   1.345 +    JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
   1.346 +
   1.347 +    RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
   1.348 +                                             lineNumber, columnNumber, nullptr, message));
   1.349 +    if (!obj)
   1.350 +        return false;
   1.351 +
   1.352 +    args.rval().setObject(*obj);
   1.353 +    return true;
   1.354 +}
   1.355 +
   1.356 +/* ES5 15.11.4.4 (NB: with subsequent errata). */
   1.357 +static bool
   1.358 +exn_toString(JSContext *cx, unsigned argc, Value *vp)
   1.359 +{
   1.360 +    JS_CHECK_RECURSION(cx, return false);
   1.361 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.362 +
   1.363 +    /* Step 2. */
   1.364 +    if (!args.thisv().isObject()) {
   1.365 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "Error");
   1.366 +        return false;
   1.367 +    }
   1.368 +
   1.369 +    /* Step 1. */
   1.370 +    RootedObject obj(cx, &args.thisv().toObject());
   1.371 +
   1.372 +    /* Step 3. */
   1.373 +    RootedValue nameVal(cx);
   1.374 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal))
   1.375 +        return false;
   1.376 +
   1.377 +    /* Step 4. */
   1.378 +    RootedString name(cx);
   1.379 +    if (nameVal.isUndefined()) {
   1.380 +        name = cx->names().Error;
   1.381 +    } else {
   1.382 +        name = ToString<CanGC>(cx, nameVal);
   1.383 +        if (!name)
   1.384 +            return false;
   1.385 +    }
   1.386 +
   1.387 +    /* Step 5. */
   1.388 +    RootedValue msgVal(cx);
   1.389 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &msgVal))
   1.390 +        return false;
   1.391 +
   1.392 +    /* Step 6. */
   1.393 +    RootedString message(cx);
   1.394 +    if (msgVal.isUndefined()) {
   1.395 +        message = cx->runtime()->emptyString;
   1.396 +    } else {
   1.397 +        message = ToString<CanGC>(cx, msgVal);
   1.398 +        if (!message)
   1.399 +            return false;
   1.400 +    }
   1.401 +
   1.402 +    /* Step 7. */
   1.403 +    if (name->empty() && message->empty()) {
   1.404 +        args.rval().setString(cx->names().Error);
   1.405 +        return true;
   1.406 +    }
   1.407 +
   1.408 +    /* Step 8. */
   1.409 +    if (name->empty()) {
   1.410 +        args.rval().setString(message);
   1.411 +        return true;
   1.412 +    }
   1.413 +
   1.414 +    /* Step 9. */
   1.415 +    if (message->empty()) {
   1.416 +        args.rval().setString(name);
   1.417 +        return true;
   1.418 +    }
   1.419 +
   1.420 +    /* Step 10. */
   1.421 +    StringBuffer sb(cx);
   1.422 +    if (!sb.append(name) || !sb.append(": ") || !sb.append(message))
   1.423 +        return false;
   1.424 +
   1.425 +    JSString *str = sb.finishString();
   1.426 +    if (!str)
   1.427 +        return false;
   1.428 +    args.rval().setString(str);
   1.429 +    return true;
   1.430 +}
   1.431 +
   1.432 +#if JS_HAS_TOSOURCE
   1.433 +/*
   1.434 + * Return a string that may eval to something similar to the original object.
   1.435 + */
   1.436 +static bool
   1.437 +exn_toSource(JSContext *cx, unsigned argc, Value *vp)
   1.438 +{
   1.439 +    JS_CHECK_RECURSION(cx, return false);
   1.440 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.441 +
   1.442 +    RootedObject obj(cx, ToObject(cx, args.thisv()));
   1.443 +    if (!obj)
   1.444 +        return false;
   1.445 +
   1.446 +    RootedValue nameVal(cx);
   1.447 +    RootedString name(cx);
   1.448 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal) ||
   1.449 +        !(name = ToString<CanGC>(cx, nameVal)))
   1.450 +    {
   1.451 +        return false;
   1.452 +    }
   1.453 +
   1.454 +    RootedValue messageVal(cx);
   1.455 +    RootedString message(cx);
   1.456 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &messageVal) ||
   1.457 +        !(message = ValueToSource(cx, messageVal)))
   1.458 +    {
   1.459 +        return false;
   1.460 +    }
   1.461 +
   1.462 +    RootedValue filenameVal(cx);
   1.463 +    RootedString filename(cx);
   1.464 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
   1.465 +        !(filename = ValueToSource(cx, filenameVal)))
   1.466 +    {
   1.467 +        return false;
   1.468 +    }
   1.469 +
   1.470 +    RootedValue linenoVal(cx);
   1.471 +    uint32_t lineno;
   1.472 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
   1.473 +        !ToUint32(cx, linenoVal, &lineno))
   1.474 +    {
   1.475 +        return false;
   1.476 +    }
   1.477 +
   1.478 +    StringBuffer sb(cx);
   1.479 +    if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
   1.480 +        return false;
   1.481 +
   1.482 +    if (!sb.append(message))
   1.483 +        return false;
   1.484 +
   1.485 +    if (!filename->empty()) {
   1.486 +        if (!sb.append(", ") || !sb.append(filename))
   1.487 +            return false;
   1.488 +    }
   1.489 +    if (lineno != 0) {
   1.490 +        /* We have a line, but no filename, add empty string */
   1.491 +        if (filename->empty() && !sb.append(", \"\""))
   1.492 +                return false;
   1.493 +
   1.494 +        JSString *linenumber = ToString<CanGC>(cx, linenoVal);
   1.495 +        if (!linenumber)
   1.496 +            return false;
   1.497 +        if (!sb.append(", ") || !sb.append(linenumber))
   1.498 +            return false;
   1.499 +    }
   1.500 +
   1.501 +    if (!sb.append("))"))
   1.502 +        return false;
   1.503 +
   1.504 +    JSString *str = sb.finishString();
   1.505 +    if (!str)
   1.506 +        return false;
   1.507 +    args.rval().setString(str);
   1.508 +    return true;
   1.509 +}
   1.510 +#endif
   1.511 +
   1.512 +static const JSFunctionSpec exception_methods[] = {
   1.513 +#if JS_HAS_TOSOURCE
   1.514 +    JS_FN(js_toSource_str,   exn_toSource,           0,0),
   1.515 +#endif
   1.516 +    JS_FN(js_toString_str,   exn_toString,           0,0),
   1.517 +    JS_FS_END
   1.518 +};
   1.519 +
   1.520 +/* JSProto_ ordering for exceptions shall match JSEXN_ constants. */
   1.521 +JS_STATIC_ASSERT(JSEXN_ERR == 0);
   1.522 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_INTERNALERR  == JSProto_InternalError);
   1.523 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_EVALERR      == JSProto_EvalError);
   1.524 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_RANGEERR     == JSProto_RangeError);
   1.525 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError);
   1.526 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_SYNTAXERR    == JSProto_SyntaxError);
   1.527 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_TYPEERR      == JSProto_TypeError);
   1.528 +JS_STATIC_ASSERT(JSProto_Error + JSEXN_URIERR       == JSProto_URIError);
   1.529 +
   1.530 +/* static */ ErrorObject *
   1.531 +ErrorObject::createProto(JSContext *cx, JS::Handle<GlobalObject*> global, JSExnType type,
   1.532 +                         JS::HandleObject proto)
   1.533 +{
   1.534 +    RootedObject errorProto(cx);
   1.535 +    errorProto = global->createBlankPrototypeInheriting(cx, &ErrorObject::class_, *proto);
   1.536 +    if (!errorProto)
   1.537 +        return nullptr;
   1.538 +
   1.539 +    Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
   1.540 +    RootedString emptyStr(cx, cx->names().empty);
   1.541 +    if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr))
   1.542 +        return nullptr;
   1.543 +
   1.544 +    // The various prototypes also have .name in addition to the normal error
   1.545 +    // instance properties.
   1.546 +    JSProtoKey key = GetExceptionProtoKey(type);
   1.547 +    RootedPropertyName name(cx, ClassName(key, cx));
   1.548 +    RootedValue nameValue(cx, StringValue(name));
   1.549 +    if (!JSObject::defineProperty(cx, err, cx->names().name, nameValue,
   1.550 +                                  JS_PropertyStub, JS_StrictPropertyStub, 0))
   1.551 +    {
   1.552 +        return nullptr;
   1.553 +    }
   1.554 +
   1.555 +    // Create the corresponding constructor.
   1.556 +    RootedFunction ctor(cx, global->createConstructor(cx, Error, name, 1,
   1.557 +                                                      JSFunction::ExtendedFinalizeKind));
   1.558 +    if (!ctor)
   1.559 +        return nullptr;
   1.560 +    ctor->setExtendedSlot(0, Int32Value(int32_t(type)));
   1.561 +
   1.562 +    if (!LinkConstructorAndPrototype(cx, ctor, err))
   1.563 +        return nullptr;
   1.564 +
   1.565 +    if (!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, err))
   1.566 +        return nullptr;
   1.567 +
   1.568 +    return err;
   1.569 +}
   1.570 +
   1.571 +JSObject *
   1.572 +js_InitExceptionClasses(JSContext *cx, HandleObject obj)
   1.573 +{
   1.574 +    JS_ASSERT(obj->is<GlobalObject>());
   1.575 +    JS_ASSERT(obj->isNative());
   1.576 +
   1.577 +    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
   1.578 +
   1.579 +    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
   1.580 +    if (!objProto)
   1.581 +        return nullptr;
   1.582 +
   1.583 +    /* Initialize the base Error class first. */
   1.584 +    RootedObject errorProto(cx, ErrorObject::createProto(cx, global, JSEXN_ERR, objProto));
   1.585 +    if (!errorProto)
   1.586 +        return nullptr;
   1.587 +
   1.588 +    /* |Error.prototype| alone has method properties. */
   1.589 +    if (!DefinePropertiesAndBrand(cx, errorProto, nullptr, exception_methods))
   1.590 +        return nullptr;
   1.591 +
   1.592 +    /* Define all remaining *Error constructors. */
   1.593 +    for (int i = JSEXN_ERR + 1; i < JSEXN_LIMIT; i++) {
   1.594 +        if (!ErrorObject::createProto(cx, global, JSExnType(i), errorProto))
   1.595 +            return nullptr;
   1.596 +    }
   1.597 +
   1.598 +    return errorProto;
   1.599 +}
   1.600 +
   1.601 +const JSErrorFormatString*
   1.602 +js_GetLocalizedErrorMessage(ExclusiveContext *cx, void *userRef, const char *locale,
   1.603 +                            const unsigned errorNumber)
   1.604 +{
   1.605 +    const JSErrorFormatString *errorString = nullptr;
   1.606 +
   1.607 +    // The locale callbacks might not be thread safe, so don't call them if
   1.608 +    // we're not on the main thread. When used with XPConnect,
   1.609 +    // |localeGetErrorMessage| will be nullptr anyways.
   1.610 +    if (cx->isJSContext() &&
   1.611 +        cx->asJSContext()->runtime()->localeCallbacks &&
   1.612 +        cx->asJSContext()->runtime()->localeCallbacks->localeGetErrorMessage)
   1.613 +    {
   1.614 +        JSLocaleCallbacks *callbacks = cx->asJSContext()->runtime()->localeCallbacks;
   1.615 +        errorString = callbacks->localeGetErrorMessage(userRef, locale, errorNumber);
   1.616 +    }
   1.617 +
   1.618 +    if (!errorString)
   1.619 +        errorString = js_GetErrorMessage(userRef, locale, errorNumber);
   1.620 +    return errorString;
   1.621 +}
   1.622 +
   1.623 +JS_FRIEND_API(const jschar*)
   1.624 +js::GetErrorTypeName(JSRuntime* rt, int16_t exnType)
   1.625 +{
   1.626 +    /*
   1.627 +     * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
   1.628 +     * is prepended before "uncaught exception: "
   1.629 +     */
   1.630 +    if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
   1.631 +        exnType == JSEXN_INTERNALERR)
   1.632 +    {
   1.633 +        return nullptr;
   1.634 +    }
   1.635 +    JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
   1.636 +    return ClassName(key, rt)->chars();
   1.637 +}
   1.638 +
   1.639 +bool
   1.640 +js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp,
   1.641 +                    JSErrorCallback callback, void *userRef)
   1.642 +{
   1.643 +    // Tell our caller to report immediately if this report is just a warning.
   1.644 +    JS_ASSERT(reportp);
   1.645 +    if (JSREPORT_IS_WARNING(reportp->flags))
   1.646 +        return false;
   1.647 +
   1.648 +    // Find the exception index associated with this error.
   1.649 +    JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
   1.650 +    const JSErrorFormatString *errorString;
   1.651 +    if (!callback || callback == js_GetErrorMessage)
   1.652 +        errorString = js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber);
   1.653 +    else
   1.654 +        errorString = callback(userRef, nullptr, errorNumber);
   1.655 +    JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
   1.656 +    MOZ_ASSERT(exnType < JSEXN_LIMIT);
   1.657 +
   1.658 +    // Return false (no exception raised) if no exception is associated
   1.659 +    // with the given error number.
   1.660 +    if (exnType == JSEXN_NONE)
   1.661 +        return false;
   1.662 +
   1.663 +    // Prevent infinite recursion.
   1.664 +    if (cx->generatingError)
   1.665 +        return false;
   1.666 +    AutoScopedAssign<bool> asa(&cx->generatingError, true);
   1.667 +
   1.668 +    // Create an exception object.
   1.669 +    RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage)
   1.670 +                                                   : JS_NewStringCopyZ(cx, message));
   1.671 +    if (!messageStr)
   1.672 +        return cx->isExceptionPending();
   1.673 +
   1.674 +    RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
   1.675 +    if (!fileName)
   1.676 +        return cx->isExceptionPending();
   1.677 +
   1.678 +    uint32_t lineNumber = reportp->lineno;
   1.679 +    uint32_t columnNumber = reportp->column;
   1.680 +
   1.681 +    RootedString stack(cx, ComputeStackString(cx));
   1.682 +    if (!stack)
   1.683 +        return cx->isExceptionPending();
   1.684 +
   1.685 +    js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
   1.686 +    if (!report)
   1.687 +        return cx->isExceptionPending();
   1.688 +
   1.689 +    RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
   1.690 +                                                   lineNumber, columnNumber, &report, messageStr));
   1.691 +    if (!errObject)
   1.692 +        return cx->isExceptionPending();
   1.693 +
   1.694 +    // Throw it.
   1.695 +    RootedValue errValue(cx, ObjectValue(*errObject));
   1.696 +    JS_SetPendingException(cx, errValue);
   1.697 +
   1.698 +    // Flag the error report passed in to indicate an exception was raised.
   1.699 +    reportp->flags |= JSREPORT_EXCEPTION;
   1.700 +    return true;
   1.701 +}
   1.702 +
   1.703 +static bool
   1.704 +IsDuckTypedErrorObject(JSContext *cx, HandleObject exnObject, const char **filename_strp)
   1.705 +{
   1.706 +    bool found;
   1.707 +    if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
   1.708 +        return false;
   1.709 +
   1.710 +    const char *filename_str = *filename_strp;
   1.711 +    if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
   1.712 +        /* DOMException duck quacks "filename" (all lowercase) */
   1.713 +        filename_str = "filename";
   1.714 +        if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found)
   1.715 +            return false;
   1.716 +    }
   1.717 +
   1.718 +    if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found)
   1.719 +        return false;
   1.720 +
   1.721 +    *filename_strp = filename_str;
   1.722 +    return true;
   1.723 +}
   1.724 +
   1.725 +JS_FRIEND_API(JSString *)
   1.726 +js::ErrorReportToString(JSContext *cx, JSErrorReport *reportp)
   1.727 +{
   1.728 +    JSExnType type = static_cast<JSExnType>(reportp->exnType);
   1.729 +    RootedString str(cx, cx->runtime()->emptyString);
   1.730 +    if (type != JSEXN_NONE)
   1.731 +        str = ClassName(GetExceptionProtoKey(type), cx);
   1.732 +    RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2));
   1.733 +    if (!str || !toAppend)
   1.734 +        return nullptr;
   1.735 +    str = ConcatStrings<CanGC>(cx, str, toAppend);
   1.736 +    if (!str)
   1.737 +        return nullptr;
   1.738 +    toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage);
   1.739 +    if (toAppend)
   1.740 +        str = ConcatStrings<CanGC>(cx, str, toAppend);
   1.741 +    return str;
   1.742 +}
   1.743 +
   1.744 +bool
   1.745 +js_ReportUncaughtException(JSContext *cx)
   1.746 +{
   1.747 +    if (!cx->isExceptionPending())
   1.748 +        return true;
   1.749 +
   1.750 +    RootedValue exn(cx);
   1.751 +    if (!cx->getPendingException(&exn))
   1.752 +        return false;
   1.753 +
   1.754 +    /*
   1.755 +     * Because ToString below could error and an exception object could become
   1.756 +     * unrooted, we must root exnObject.  Later, if exnObject is non-null, we
   1.757 +     * need to root other intermediates, so allocate an operand stack segment
   1.758 +     * to protect all of these values.
   1.759 +     */
   1.760 +    RootedObject exnObject(cx);
   1.761 +    if (JSVAL_IS_PRIMITIVE(exn)) {
   1.762 +        exnObject = nullptr;
   1.763 +    } else {
   1.764 +        exnObject = JSVAL_TO_OBJECT(exn);
   1.765 +    }
   1.766 +
   1.767 +    JS_ClearPendingException(cx);
   1.768 +    JSErrorReport *reportp = exnObject ? js_ErrorFromException(cx, exnObject)
   1.769 +                                       : nullptr;
   1.770 +
   1.771 +    // Be careful not to invoke ToString if we've already successfully extracted
   1.772 +    // an error report, since the exception might be wrapped in a security
   1.773 +    // wrapper, and ToString-ing it might throw.
   1.774 +    RootedString str(cx);
   1.775 +    if (reportp)
   1.776 +        str = ErrorReportToString(cx, reportp);
   1.777 +    else
   1.778 +        str = ToString<CanGC>(cx, exn);
   1.779 +
   1.780 +    JSErrorReport report;
   1.781 +
   1.782 +    // If js_ErrorFromException didn't get us a JSErrorReport, then the object
   1.783 +    // was not an ErrorObject, security-wrapped or otherwise. However, it might
   1.784 +    // still quack like one. Give duck-typing a chance.
   1.785 +    const char *filename_str = js_fileName_str;
   1.786 +    JSAutoByteString filename;
   1.787 +    if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
   1.788 +    {
   1.789 +        // Temporary value for pulling properties off of duck-typed objects.
   1.790 +        RootedValue val(cx);
   1.791 +
   1.792 +        RootedString name(cx);
   1.793 +        if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
   1.794 +            name = val.toString();
   1.795 +
   1.796 +        RootedString msg(cx);
   1.797 +        if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
   1.798 +            msg = val.toString();
   1.799 +
   1.800 +        // If we have the right fields, override the ToString we performed on
   1.801 +        // the exception object above with something built out of its quacks
   1.802 +        // (i.e. as much of |NameQuack: MessageQuack| as we can make).
   1.803 +        //
   1.804 +        // It would be nice to use ErrorReportToString here, but we can't quite
   1.805 +        // do it - mostly because we'd need to figure out what JSExnType |name|
   1.806 +        // corresponds to, which may not be any JSExnType at all.
   1.807 +        if (name && msg) {
   1.808 +            RootedString colon(cx, JS_NewStringCopyZ(cx, ": "));
   1.809 +            if (!colon)
   1.810 +                return false;
   1.811 +            RootedString nameColon(cx, ConcatStrings<CanGC>(cx, name, colon));
   1.812 +            if (!nameColon)
   1.813 +                return false;
   1.814 +            str = ConcatStrings<CanGC>(cx, nameColon, msg);
   1.815 +            if (!str)
   1.816 +                return false;
   1.817 +        } else if (name) {
   1.818 +            str = name;
   1.819 +        } else if (msg) {
   1.820 +            str = msg;
   1.821 +        }
   1.822 +
   1.823 +        if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
   1.824 +            JSString *tmp = ToString<CanGC>(cx, val);
   1.825 +            if (tmp)
   1.826 +                filename.encodeLatin1(cx, tmp);
   1.827 +        }
   1.828 +
   1.829 +        uint32_t lineno;
   1.830 +        if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
   1.831 +            !ToUint32(cx, val, &lineno))
   1.832 +        {
   1.833 +            lineno = 0;
   1.834 +        }
   1.835 +
   1.836 +        uint32_t column;
   1.837 +        if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
   1.838 +            !ToUint32(cx, val, &column))
   1.839 +        {
   1.840 +            column = 0;
   1.841 +        }
   1.842 +
   1.843 +        reportp = &report;
   1.844 +        PodZero(&report);
   1.845 +        report.filename = filename.ptr();
   1.846 +        report.lineno = (unsigned) lineno;
   1.847 +        report.exnType = int16_t(JSEXN_NONE);
   1.848 +        report.column = (unsigned) column;
   1.849 +        if (str) {
   1.850 +            // Note that using |str| for |ucmessage| here is kind of wrong,
   1.851 +            // because |str| is supposed to be of the format
   1.852 +            // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
   1.853 +            // correspond to |ErrorMessage|. But this is what we've historically
   1.854 +            // done for duck-typed error objects.
   1.855 +            //
   1.856 +            // If only this stuff could get specced one day...
   1.857 +            if (JSFlatString *flat = str->ensureFlat(cx))
   1.858 +                report.ucmessage = flat->chars();
   1.859 +        }
   1.860 +    }
   1.861 +
   1.862 +    JSAutoByteString bytesStorage;
   1.863 +    const char *bytes = nullptr;
   1.864 +    if (str)
   1.865 +        bytes = bytesStorage.encodeLatin1(cx, str);
   1.866 +    if (!bytes)
   1.867 +        bytes = "unknown (can't convert to string)";
   1.868 +
   1.869 +    if (!reportp) {
   1.870 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
   1.871 +                             JSMSG_UNCAUGHT_EXCEPTION, bytes);
   1.872 +    } else {
   1.873 +        /* Flag the error as an exception. */
   1.874 +        reportp->flags |= JSREPORT_EXCEPTION;
   1.875 +
   1.876 +        /* Pass the exception object. */
   1.877 +        JS_SetPendingException(cx, exn);
   1.878 +        CallErrorReporter(cx, bytes, reportp);
   1.879 +    }
   1.880 +
   1.881 +    JS_ClearPendingException(cx);
   1.882 +    return true;
   1.883 +}
   1.884 +
   1.885 +JSObject *
   1.886 +js_CopyErrorObject(JSContext *cx, Handle<ErrorObject*> err, HandleObject scope)
   1.887 +{
   1.888 +    assertSameCompartment(cx, scope);
   1.889 +
   1.890 +    js::ScopedJSFreePtr<JSErrorReport> copyReport;
   1.891 +    if (JSErrorReport *errorReport = err->getErrorReport()) {
   1.892 +        copyReport = CopyErrorReport(cx, errorReport);
   1.893 +        if (!copyReport)
   1.894 +            return nullptr;
   1.895 +    }
   1.896 +
   1.897 +    RootedString message(cx, err->getMessage());
   1.898 +    if (message && !cx->compartment()->wrap(cx, message.address()))
   1.899 +        return nullptr;
   1.900 +    RootedString fileName(cx, err->fileName(cx));
   1.901 +    if (!cx->compartment()->wrap(cx, fileName.address()))
   1.902 +        return nullptr;
   1.903 +    RootedString stack(cx, err->stack(cx));
   1.904 +    if (!cx->compartment()->wrap(cx, stack.address()))
   1.905 +        return nullptr;
   1.906 +    uint32_t lineNumber = err->lineNumber();
   1.907 +    uint32_t columnNumber = err->columnNumber();
   1.908 +    JSExnType errorType = err->type();
   1.909 +
   1.910 +    // Create the Error object.
   1.911 +    return ErrorObject::create(cx, errorType, stack, fileName,
   1.912 +                               lineNumber, columnNumber, &copyReport, message);
   1.913 +}
   1.914 +
   1.915 +JS_PUBLIC_API(bool)
   1.916 +JS::CreateTypeError(JSContext *cx, HandleString stack, HandleString fileName,
   1.917 +                    uint32_t lineNumber, uint32_t columnNumber, JSErrorReport *report,
   1.918 +                    HandleString message, MutableHandleValue rval)
   1.919 +{
   1.920 +    assertSameCompartment(cx, stack, fileName, message);
   1.921 +    js::ScopedJSFreePtr<JSErrorReport> rep;
   1.922 +    if (report)
   1.923 +        rep = CopyErrorReport(cx, report);
   1.924 +
   1.925 +    RootedObject obj(cx,
   1.926 +        js::ErrorObject::create(cx, JSEXN_TYPEERR, stack, fileName,
   1.927 +                                lineNumber, columnNumber, &rep, message));
   1.928 +    if (!obj)
   1.929 +        return false;
   1.930 +
   1.931 +    rval.setObject(*obj);
   1.932 +    return true;
   1.933 +}

mercurial