js/src/jsexn.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 * vim: set ts=8 sts=4 et sw=4 tw=99:
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 * JS standard exception implementation.
michael@0 9 */
michael@0 10
michael@0 11 #include "jsexn.h"
michael@0 12
michael@0 13 #include "mozilla/ArrayUtils.h"
michael@0 14 #include "mozilla/PodOperations.h"
michael@0 15
michael@0 16 #include <string.h>
michael@0 17
michael@0 18 #include "jsapi.h"
michael@0 19 #include "jscntxt.h"
michael@0 20 #include "jsfun.h"
michael@0 21 #include "jsnum.h"
michael@0 22 #include "jsobj.h"
michael@0 23 #include "jsscript.h"
michael@0 24 #include "jstypes.h"
michael@0 25 #include "jsutil.h"
michael@0 26 #include "jswrapper.h"
michael@0 27
michael@0 28 #include "gc/Marking.h"
michael@0 29 #include "vm/ErrorObject.h"
michael@0 30 #include "vm/GlobalObject.h"
michael@0 31 #include "vm/StringBuffer.h"
michael@0 32
michael@0 33 #include "jsobjinlines.h"
michael@0 34
michael@0 35 #include "vm/ErrorObject-inl.h"
michael@0 36
michael@0 37 using namespace js;
michael@0 38 using namespace js::gc;
michael@0 39 using namespace js::types;
michael@0 40
michael@0 41 using mozilla::ArrayLength;
michael@0 42 using mozilla::PodArrayZero;
michael@0 43 using mozilla::PodZero;
michael@0 44
michael@0 45 static void
michael@0 46 exn_finalize(FreeOp *fop, JSObject *obj);
michael@0 47
michael@0 48 const Class ErrorObject::class_ = {
michael@0 49 js_Error_str,
michael@0 50 JSCLASS_IMPLEMENTS_BARRIERS |
michael@0 51 JSCLASS_HAS_CACHED_PROTO(JSProto_Error) |
michael@0 52 JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS),
michael@0 53 JS_PropertyStub, /* addProperty */
michael@0 54 JS_DeletePropertyStub, /* delProperty */
michael@0 55 JS_PropertyStub, /* getProperty */
michael@0 56 JS_StrictPropertyStub, /* setProperty */
michael@0 57 JS_EnumerateStub,
michael@0 58 JS_ResolveStub,
michael@0 59 JS_ConvertStub,
michael@0 60 exn_finalize,
michael@0 61 nullptr, /* call */
michael@0 62 nullptr, /* hasInstance */
michael@0 63 nullptr /* construct */
michael@0 64 };
michael@0 65
michael@0 66 JSErrorReport *
michael@0 67 js::CopyErrorReport(JSContext *cx, JSErrorReport *report)
michael@0 68 {
michael@0 69 /*
michael@0 70 * We use a single malloc block to make a deep copy of JSErrorReport with
michael@0 71 * the following layout:
michael@0 72 * JSErrorReport
michael@0 73 * array of copies of report->messageArgs
michael@0 74 * jschar array with characters for all messageArgs
michael@0 75 * jschar array with characters for ucmessage
michael@0 76 * jschar array with characters for uclinebuf and uctokenptr
michael@0 77 * char array with characters for linebuf and tokenptr
michael@0 78 * char array with characters for filename
michael@0 79 * Such layout together with the properties enforced by the following
michael@0 80 * asserts does not need any extra alignment padding.
michael@0 81 */
michael@0 82 JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char *) == 0);
michael@0 83 JS_STATIC_ASSERT(sizeof(const char *) % sizeof(jschar) == 0);
michael@0 84
michael@0 85 size_t filenameSize;
michael@0 86 size_t linebufSize;
michael@0 87 size_t uclinebufSize;
michael@0 88 size_t ucmessageSize;
michael@0 89 size_t i, argsArraySize, argsCopySize, argSize;
michael@0 90 size_t mallocSize;
michael@0 91 JSErrorReport *copy;
michael@0 92 uint8_t *cursor;
michael@0 93
michael@0 94 #define JS_CHARS_SIZE(jschars) ((js_strlen(jschars) + 1) * sizeof(jschar))
michael@0 95
michael@0 96 filenameSize = report->filename ? strlen(report->filename) + 1 : 0;
michael@0 97 linebufSize = report->linebuf ? strlen(report->linebuf) + 1 : 0;
michael@0 98 uclinebufSize = report->uclinebuf ? JS_CHARS_SIZE(report->uclinebuf) : 0;
michael@0 99 ucmessageSize = 0;
michael@0 100 argsArraySize = 0;
michael@0 101 argsCopySize = 0;
michael@0 102 if (report->ucmessage) {
michael@0 103 ucmessageSize = JS_CHARS_SIZE(report->ucmessage);
michael@0 104 if (report->messageArgs) {
michael@0 105 for (i = 0; report->messageArgs[i]; ++i)
michael@0 106 argsCopySize += JS_CHARS_SIZE(report->messageArgs[i]);
michael@0 107
michael@0 108 /* Non-null messageArgs should have at least one non-null arg. */
michael@0 109 JS_ASSERT(i != 0);
michael@0 110 argsArraySize = (i + 1) * sizeof(const jschar *);
michael@0 111 }
michael@0 112 }
michael@0 113
michael@0 114 /*
michael@0 115 * The mallocSize can not overflow since it represents the sum of the
michael@0 116 * sizes of already allocated objects.
michael@0 117 */
michael@0 118 mallocSize = sizeof(JSErrorReport) + argsArraySize + argsCopySize +
michael@0 119 ucmessageSize + uclinebufSize + linebufSize + filenameSize;
michael@0 120 cursor = cx->pod_malloc<uint8_t>(mallocSize);
michael@0 121 if (!cursor)
michael@0 122 return nullptr;
michael@0 123
michael@0 124 copy = (JSErrorReport *)cursor;
michael@0 125 memset(cursor, 0, sizeof(JSErrorReport));
michael@0 126 cursor += sizeof(JSErrorReport);
michael@0 127
michael@0 128 if (argsArraySize != 0) {
michael@0 129 copy->messageArgs = (const jschar **)cursor;
michael@0 130 cursor += argsArraySize;
michael@0 131 for (i = 0; report->messageArgs[i]; ++i) {
michael@0 132 copy->messageArgs[i] = (const jschar *)cursor;
michael@0 133 argSize = JS_CHARS_SIZE(report->messageArgs[i]);
michael@0 134 js_memcpy(cursor, report->messageArgs[i], argSize);
michael@0 135 cursor += argSize;
michael@0 136 }
michael@0 137 copy->messageArgs[i] = nullptr;
michael@0 138 JS_ASSERT(cursor == (uint8_t *)copy->messageArgs[0] + argsCopySize);
michael@0 139 }
michael@0 140
michael@0 141 if (report->ucmessage) {
michael@0 142 copy->ucmessage = (const jschar *)cursor;
michael@0 143 js_memcpy(cursor, report->ucmessage, ucmessageSize);
michael@0 144 cursor += ucmessageSize;
michael@0 145 }
michael@0 146
michael@0 147 if (report->uclinebuf) {
michael@0 148 copy->uclinebuf = (const jschar *)cursor;
michael@0 149 js_memcpy(cursor, report->uclinebuf, uclinebufSize);
michael@0 150 cursor += uclinebufSize;
michael@0 151 if (report->uctokenptr) {
michael@0 152 copy->uctokenptr = copy->uclinebuf + (report->uctokenptr -
michael@0 153 report->uclinebuf);
michael@0 154 }
michael@0 155 }
michael@0 156
michael@0 157 if (report->linebuf) {
michael@0 158 copy->linebuf = (const char *)cursor;
michael@0 159 js_memcpy(cursor, report->linebuf, linebufSize);
michael@0 160 cursor += linebufSize;
michael@0 161 if (report->tokenptr) {
michael@0 162 copy->tokenptr = copy->linebuf + (report->tokenptr -
michael@0 163 report->linebuf);
michael@0 164 }
michael@0 165 }
michael@0 166
michael@0 167 if (report->filename) {
michael@0 168 copy->filename = (const char *)cursor;
michael@0 169 js_memcpy(cursor, report->filename, filenameSize);
michael@0 170 }
michael@0 171 JS_ASSERT(cursor + filenameSize == (uint8_t *)copy + mallocSize);
michael@0 172
michael@0 173 /* HOLD called by the destination error object. */
michael@0 174 copy->originPrincipals = report->originPrincipals;
michael@0 175
michael@0 176 /* Copy non-pointer members. */
michael@0 177 copy->lineno = report->lineno;
michael@0 178 copy->column = report->column;
michael@0 179 copy->errorNumber = report->errorNumber;
michael@0 180 copy->exnType = report->exnType;
michael@0 181
michael@0 182 /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */
michael@0 183 copy->flags = report->flags;
michael@0 184
michael@0 185 #undef JS_CHARS_SIZE
michael@0 186 return copy;
michael@0 187 }
michael@0 188
michael@0 189 struct SuppressErrorsGuard
michael@0 190 {
michael@0 191 JSContext *cx;
michael@0 192 JSErrorReporter prevReporter;
michael@0 193 JS::AutoSaveExceptionState prevState;
michael@0 194
michael@0 195 SuppressErrorsGuard(JSContext *cx)
michael@0 196 : cx(cx),
michael@0 197 prevReporter(JS_SetErrorReporter(cx, nullptr)),
michael@0 198 prevState(cx)
michael@0 199 {}
michael@0 200
michael@0 201 ~SuppressErrorsGuard()
michael@0 202 {
michael@0 203 JS_SetErrorReporter(cx, prevReporter);
michael@0 204 }
michael@0 205 };
michael@0 206
michael@0 207 JSString *
michael@0 208 js::ComputeStackString(JSContext *cx)
michael@0 209 {
michael@0 210 StringBuffer sb(cx);
michael@0 211
michael@0 212 {
michael@0 213 RootedAtom atom(cx);
michael@0 214 SuppressErrorsGuard seg(cx);
michael@0 215 for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED,
michael@0 216 cx->compartment()->principals);
michael@0 217 !i.done();
michael@0 218 ++i)
michael@0 219 {
michael@0 220 /* First append the function name, if any. */
michael@0 221 if (i.isNonEvalFunctionFrame())
michael@0 222 atom = i.functionDisplayAtom();
michael@0 223 else
michael@0 224 atom = nullptr;
michael@0 225 if (atom && !sb.append(atom))
michael@0 226 return nullptr;
michael@0 227
michael@0 228 /* Next a @ separating function name from source location. */
michael@0 229 if (!sb.append('@'))
michael@0 230 return nullptr;
michael@0 231
michael@0 232 /* Now the filename. */
michael@0 233 const char *cfilename = i.scriptFilename();
michael@0 234 if (!cfilename)
michael@0 235 cfilename = "";
michael@0 236 if (!sb.appendInflated(cfilename, strlen(cfilename)))
michael@0 237 return nullptr;
michael@0 238
michael@0 239 uint32_t column = 0;
michael@0 240 uint32_t line = i.computeLine(&column);
michael@0 241 // Now the line number
michael@0 242 if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb))
michael@0 243 return nullptr;
michael@0 244
michael@0 245 // Finally, : followed by the column number (1-based, as in other browsers)
michael@0 246 // and a newline.
michael@0 247 if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(column + 1), sb) ||
michael@0 248 !sb.append('\n'))
michael@0 249 {
michael@0 250 return nullptr;
michael@0 251 }
michael@0 252
michael@0 253 /*
michael@0 254 * Cut off the stack if it gets too deep (most commonly for
michael@0 255 * infinite recursion errors).
michael@0 256 */
michael@0 257 const size_t MaxReportedStackDepth = 1u << 20;
michael@0 258 if (sb.length() > MaxReportedStackDepth)
michael@0 259 break;
michael@0 260 }
michael@0 261 }
michael@0 262
michael@0 263 return sb.finishString();
michael@0 264 }
michael@0 265
michael@0 266 static void
michael@0 267 exn_finalize(FreeOp *fop, JSObject *obj)
michael@0 268 {
michael@0 269 if (JSErrorReport *report = obj->as<ErrorObject>().getErrorReport()) {
michael@0 270 /* These were held by ErrorObject::init. */
michael@0 271 if (JSPrincipals *prin = report->originPrincipals)
michael@0 272 JS_DropPrincipals(fop->runtime(), prin);
michael@0 273 fop->free_(report);
michael@0 274 }
michael@0 275 }
michael@0 276
michael@0 277 JSErrorReport *
michael@0 278 js_ErrorFromException(JSContext *cx, HandleObject objArg)
michael@0 279 {
michael@0 280 // It's ok to UncheckedUnwrap here, since all we do is get the
michael@0 281 // JSErrorReport, and consumers are careful with the information they get
michael@0 282 // from that anyway. Anyone doing things that would expose anything in the
michael@0 283 // JSErrorReport to page script either does a security check on the
michael@0 284 // JSErrorReport's principal or also tries to do toString on our object and
michael@0 285 // will fail if they can't unwrap it.
michael@0 286 RootedObject obj(cx, UncheckedUnwrap(objArg));
michael@0 287 if (!obj->is<ErrorObject>())
michael@0 288 return nullptr;
michael@0 289
michael@0 290 return obj->as<ErrorObject>().getOrCreateErrorReport(cx);
michael@0 291 }
michael@0 292
michael@0 293 static bool
michael@0 294 Error(JSContext *cx, unsigned argc, Value *vp)
michael@0 295 {
michael@0 296 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 297
michael@0 298 /* Compute the error message, if any. */
michael@0 299 RootedString message(cx, nullptr);
michael@0 300 if (args.hasDefined(0)) {
michael@0 301 message = ToString<CanGC>(cx, args[0]);
michael@0 302 if (!message)
michael@0 303 return false;
michael@0 304 }
michael@0 305
michael@0 306 /* Find the scripted caller. */
michael@0 307 NonBuiltinFrameIter iter(cx);
michael@0 308
michael@0 309 /* Set the 'fileName' property. */
michael@0 310 RootedString fileName(cx);
michael@0 311 if (args.length() > 1) {
michael@0 312 fileName = ToString<CanGC>(cx, args[1]);
michael@0 313 } else {
michael@0 314 fileName = cx->runtime()->emptyString;
michael@0 315 if (!iter.done()) {
michael@0 316 if (const char *cfilename = iter.scriptFilename())
michael@0 317 fileName = JS_NewStringCopyZ(cx, cfilename);
michael@0 318 }
michael@0 319 }
michael@0 320 if (!fileName)
michael@0 321 return false;
michael@0 322
michael@0 323 /* Set the 'lineNumber' property. */
michael@0 324 uint32_t lineNumber, columnNumber = 0;
michael@0 325 if (args.length() > 2) {
michael@0 326 if (!ToUint32(cx, args[2], &lineNumber))
michael@0 327 return false;
michael@0 328 } else {
michael@0 329 lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
michael@0 330 }
michael@0 331
michael@0 332 Rooted<JSString*> stack(cx, ComputeStackString(cx));
michael@0 333 if (!stack)
michael@0 334 return false;
michael@0 335
michael@0 336 /*
michael@0 337 * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
michael@0 338 * called as functions, without operator new. But as we do not give
michael@0 339 * each constructor a distinct JSClass, we must get the exception type
michael@0 340 * ourselves.
michael@0 341 */
michael@0 342 JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
michael@0 343
michael@0 344 RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
michael@0 345 lineNumber, columnNumber, nullptr, message));
michael@0 346 if (!obj)
michael@0 347 return false;
michael@0 348
michael@0 349 args.rval().setObject(*obj);
michael@0 350 return true;
michael@0 351 }
michael@0 352
michael@0 353 /* ES5 15.11.4.4 (NB: with subsequent errata). */
michael@0 354 static bool
michael@0 355 exn_toString(JSContext *cx, unsigned argc, Value *vp)
michael@0 356 {
michael@0 357 JS_CHECK_RECURSION(cx, return false);
michael@0 358 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 359
michael@0 360 /* Step 2. */
michael@0 361 if (!args.thisv().isObject()) {
michael@0 362 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "Error");
michael@0 363 return false;
michael@0 364 }
michael@0 365
michael@0 366 /* Step 1. */
michael@0 367 RootedObject obj(cx, &args.thisv().toObject());
michael@0 368
michael@0 369 /* Step 3. */
michael@0 370 RootedValue nameVal(cx);
michael@0 371 if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal))
michael@0 372 return false;
michael@0 373
michael@0 374 /* Step 4. */
michael@0 375 RootedString name(cx);
michael@0 376 if (nameVal.isUndefined()) {
michael@0 377 name = cx->names().Error;
michael@0 378 } else {
michael@0 379 name = ToString<CanGC>(cx, nameVal);
michael@0 380 if (!name)
michael@0 381 return false;
michael@0 382 }
michael@0 383
michael@0 384 /* Step 5. */
michael@0 385 RootedValue msgVal(cx);
michael@0 386 if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &msgVal))
michael@0 387 return false;
michael@0 388
michael@0 389 /* Step 6. */
michael@0 390 RootedString message(cx);
michael@0 391 if (msgVal.isUndefined()) {
michael@0 392 message = cx->runtime()->emptyString;
michael@0 393 } else {
michael@0 394 message = ToString<CanGC>(cx, msgVal);
michael@0 395 if (!message)
michael@0 396 return false;
michael@0 397 }
michael@0 398
michael@0 399 /* Step 7. */
michael@0 400 if (name->empty() && message->empty()) {
michael@0 401 args.rval().setString(cx->names().Error);
michael@0 402 return true;
michael@0 403 }
michael@0 404
michael@0 405 /* Step 8. */
michael@0 406 if (name->empty()) {
michael@0 407 args.rval().setString(message);
michael@0 408 return true;
michael@0 409 }
michael@0 410
michael@0 411 /* Step 9. */
michael@0 412 if (message->empty()) {
michael@0 413 args.rval().setString(name);
michael@0 414 return true;
michael@0 415 }
michael@0 416
michael@0 417 /* Step 10. */
michael@0 418 StringBuffer sb(cx);
michael@0 419 if (!sb.append(name) || !sb.append(": ") || !sb.append(message))
michael@0 420 return false;
michael@0 421
michael@0 422 JSString *str = sb.finishString();
michael@0 423 if (!str)
michael@0 424 return false;
michael@0 425 args.rval().setString(str);
michael@0 426 return true;
michael@0 427 }
michael@0 428
michael@0 429 #if JS_HAS_TOSOURCE
michael@0 430 /*
michael@0 431 * Return a string that may eval to something similar to the original object.
michael@0 432 */
michael@0 433 static bool
michael@0 434 exn_toSource(JSContext *cx, unsigned argc, Value *vp)
michael@0 435 {
michael@0 436 JS_CHECK_RECURSION(cx, return false);
michael@0 437 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 438
michael@0 439 RootedObject obj(cx, ToObject(cx, args.thisv()));
michael@0 440 if (!obj)
michael@0 441 return false;
michael@0 442
michael@0 443 RootedValue nameVal(cx);
michael@0 444 RootedString name(cx);
michael@0 445 if (!JSObject::getProperty(cx, obj, obj, cx->names().name, &nameVal) ||
michael@0 446 !(name = ToString<CanGC>(cx, nameVal)))
michael@0 447 {
michael@0 448 return false;
michael@0 449 }
michael@0 450
michael@0 451 RootedValue messageVal(cx);
michael@0 452 RootedString message(cx);
michael@0 453 if (!JSObject::getProperty(cx, obj, obj, cx->names().message, &messageVal) ||
michael@0 454 !(message = ValueToSource(cx, messageVal)))
michael@0 455 {
michael@0 456 return false;
michael@0 457 }
michael@0 458
michael@0 459 RootedValue filenameVal(cx);
michael@0 460 RootedString filename(cx);
michael@0 461 if (!JSObject::getProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
michael@0 462 !(filename = ValueToSource(cx, filenameVal)))
michael@0 463 {
michael@0 464 return false;
michael@0 465 }
michael@0 466
michael@0 467 RootedValue linenoVal(cx);
michael@0 468 uint32_t lineno;
michael@0 469 if (!JSObject::getProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
michael@0 470 !ToUint32(cx, linenoVal, &lineno))
michael@0 471 {
michael@0 472 return false;
michael@0 473 }
michael@0 474
michael@0 475 StringBuffer sb(cx);
michael@0 476 if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
michael@0 477 return false;
michael@0 478
michael@0 479 if (!sb.append(message))
michael@0 480 return false;
michael@0 481
michael@0 482 if (!filename->empty()) {
michael@0 483 if (!sb.append(", ") || !sb.append(filename))
michael@0 484 return false;
michael@0 485 }
michael@0 486 if (lineno != 0) {
michael@0 487 /* We have a line, but no filename, add empty string */
michael@0 488 if (filename->empty() && !sb.append(", \"\""))
michael@0 489 return false;
michael@0 490
michael@0 491 JSString *linenumber = ToString<CanGC>(cx, linenoVal);
michael@0 492 if (!linenumber)
michael@0 493 return false;
michael@0 494 if (!sb.append(", ") || !sb.append(linenumber))
michael@0 495 return false;
michael@0 496 }
michael@0 497
michael@0 498 if (!sb.append("))"))
michael@0 499 return false;
michael@0 500
michael@0 501 JSString *str = sb.finishString();
michael@0 502 if (!str)
michael@0 503 return false;
michael@0 504 args.rval().setString(str);
michael@0 505 return true;
michael@0 506 }
michael@0 507 #endif
michael@0 508
michael@0 509 static const JSFunctionSpec exception_methods[] = {
michael@0 510 #if JS_HAS_TOSOURCE
michael@0 511 JS_FN(js_toSource_str, exn_toSource, 0,0),
michael@0 512 #endif
michael@0 513 JS_FN(js_toString_str, exn_toString, 0,0),
michael@0 514 JS_FS_END
michael@0 515 };
michael@0 516
michael@0 517 /* JSProto_ ordering for exceptions shall match JSEXN_ constants. */
michael@0 518 JS_STATIC_ASSERT(JSEXN_ERR == 0);
michael@0 519 JS_STATIC_ASSERT(JSProto_Error + JSEXN_INTERNALERR == JSProto_InternalError);
michael@0 520 JS_STATIC_ASSERT(JSProto_Error + JSEXN_EVALERR == JSProto_EvalError);
michael@0 521 JS_STATIC_ASSERT(JSProto_Error + JSEXN_RANGEERR == JSProto_RangeError);
michael@0 522 JS_STATIC_ASSERT(JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError);
michael@0 523 JS_STATIC_ASSERT(JSProto_Error + JSEXN_SYNTAXERR == JSProto_SyntaxError);
michael@0 524 JS_STATIC_ASSERT(JSProto_Error + JSEXN_TYPEERR == JSProto_TypeError);
michael@0 525 JS_STATIC_ASSERT(JSProto_Error + JSEXN_URIERR == JSProto_URIError);
michael@0 526
michael@0 527 /* static */ ErrorObject *
michael@0 528 ErrorObject::createProto(JSContext *cx, JS::Handle<GlobalObject*> global, JSExnType type,
michael@0 529 JS::HandleObject proto)
michael@0 530 {
michael@0 531 RootedObject errorProto(cx);
michael@0 532 errorProto = global->createBlankPrototypeInheriting(cx, &ErrorObject::class_, *proto);
michael@0 533 if (!errorProto)
michael@0 534 return nullptr;
michael@0 535
michael@0 536 Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
michael@0 537 RootedString emptyStr(cx, cx->names().empty);
michael@0 538 if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr))
michael@0 539 return nullptr;
michael@0 540
michael@0 541 // The various prototypes also have .name in addition to the normal error
michael@0 542 // instance properties.
michael@0 543 JSProtoKey key = GetExceptionProtoKey(type);
michael@0 544 RootedPropertyName name(cx, ClassName(key, cx));
michael@0 545 RootedValue nameValue(cx, StringValue(name));
michael@0 546 if (!JSObject::defineProperty(cx, err, cx->names().name, nameValue,
michael@0 547 JS_PropertyStub, JS_StrictPropertyStub, 0))
michael@0 548 {
michael@0 549 return nullptr;
michael@0 550 }
michael@0 551
michael@0 552 // Create the corresponding constructor.
michael@0 553 RootedFunction ctor(cx, global->createConstructor(cx, Error, name, 1,
michael@0 554 JSFunction::ExtendedFinalizeKind));
michael@0 555 if (!ctor)
michael@0 556 return nullptr;
michael@0 557 ctor->setExtendedSlot(0, Int32Value(int32_t(type)));
michael@0 558
michael@0 559 if (!LinkConstructorAndPrototype(cx, ctor, err))
michael@0 560 return nullptr;
michael@0 561
michael@0 562 if (!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, err))
michael@0 563 return nullptr;
michael@0 564
michael@0 565 return err;
michael@0 566 }
michael@0 567
michael@0 568 JSObject *
michael@0 569 js_InitExceptionClasses(JSContext *cx, HandleObject obj)
michael@0 570 {
michael@0 571 JS_ASSERT(obj->is<GlobalObject>());
michael@0 572 JS_ASSERT(obj->isNative());
michael@0 573
michael@0 574 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
michael@0 575
michael@0 576 RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
michael@0 577 if (!objProto)
michael@0 578 return nullptr;
michael@0 579
michael@0 580 /* Initialize the base Error class first. */
michael@0 581 RootedObject errorProto(cx, ErrorObject::createProto(cx, global, JSEXN_ERR, objProto));
michael@0 582 if (!errorProto)
michael@0 583 return nullptr;
michael@0 584
michael@0 585 /* |Error.prototype| alone has method properties. */
michael@0 586 if (!DefinePropertiesAndBrand(cx, errorProto, nullptr, exception_methods))
michael@0 587 return nullptr;
michael@0 588
michael@0 589 /* Define all remaining *Error constructors. */
michael@0 590 for (int i = JSEXN_ERR + 1; i < JSEXN_LIMIT; i++) {
michael@0 591 if (!ErrorObject::createProto(cx, global, JSExnType(i), errorProto))
michael@0 592 return nullptr;
michael@0 593 }
michael@0 594
michael@0 595 return errorProto;
michael@0 596 }
michael@0 597
michael@0 598 const JSErrorFormatString*
michael@0 599 js_GetLocalizedErrorMessage(ExclusiveContext *cx, void *userRef, const char *locale,
michael@0 600 const unsigned errorNumber)
michael@0 601 {
michael@0 602 const JSErrorFormatString *errorString = nullptr;
michael@0 603
michael@0 604 // The locale callbacks might not be thread safe, so don't call them if
michael@0 605 // we're not on the main thread. When used with XPConnect,
michael@0 606 // |localeGetErrorMessage| will be nullptr anyways.
michael@0 607 if (cx->isJSContext() &&
michael@0 608 cx->asJSContext()->runtime()->localeCallbacks &&
michael@0 609 cx->asJSContext()->runtime()->localeCallbacks->localeGetErrorMessage)
michael@0 610 {
michael@0 611 JSLocaleCallbacks *callbacks = cx->asJSContext()->runtime()->localeCallbacks;
michael@0 612 errorString = callbacks->localeGetErrorMessage(userRef, locale, errorNumber);
michael@0 613 }
michael@0 614
michael@0 615 if (!errorString)
michael@0 616 errorString = js_GetErrorMessage(userRef, locale, errorNumber);
michael@0 617 return errorString;
michael@0 618 }
michael@0 619
michael@0 620 JS_FRIEND_API(const jschar*)
michael@0 621 js::GetErrorTypeName(JSRuntime* rt, int16_t exnType)
michael@0 622 {
michael@0 623 /*
michael@0 624 * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
michael@0 625 * is prepended before "uncaught exception: "
michael@0 626 */
michael@0 627 if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
michael@0 628 exnType == JSEXN_INTERNALERR)
michael@0 629 {
michael@0 630 return nullptr;
michael@0 631 }
michael@0 632 JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
michael@0 633 return ClassName(key, rt)->chars();
michael@0 634 }
michael@0 635
michael@0 636 bool
michael@0 637 js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp,
michael@0 638 JSErrorCallback callback, void *userRef)
michael@0 639 {
michael@0 640 // Tell our caller to report immediately if this report is just a warning.
michael@0 641 JS_ASSERT(reportp);
michael@0 642 if (JSREPORT_IS_WARNING(reportp->flags))
michael@0 643 return false;
michael@0 644
michael@0 645 // Find the exception index associated with this error.
michael@0 646 JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
michael@0 647 const JSErrorFormatString *errorString;
michael@0 648 if (!callback || callback == js_GetErrorMessage)
michael@0 649 errorString = js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber);
michael@0 650 else
michael@0 651 errorString = callback(userRef, nullptr, errorNumber);
michael@0 652 JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
michael@0 653 MOZ_ASSERT(exnType < JSEXN_LIMIT);
michael@0 654
michael@0 655 // Return false (no exception raised) if no exception is associated
michael@0 656 // with the given error number.
michael@0 657 if (exnType == JSEXN_NONE)
michael@0 658 return false;
michael@0 659
michael@0 660 // Prevent infinite recursion.
michael@0 661 if (cx->generatingError)
michael@0 662 return false;
michael@0 663 AutoScopedAssign<bool> asa(&cx->generatingError, true);
michael@0 664
michael@0 665 // Create an exception object.
michael@0 666 RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage)
michael@0 667 : JS_NewStringCopyZ(cx, message));
michael@0 668 if (!messageStr)
michael@0 669 return cx->isExceptionPending();
michael@0 670
michael@0 671 RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
michael@0 672 if (!fileName)
michael@0 673 return cx->isExceptionPending();
michael@0 674
michael@0 675 uint32_t lineNumber = reportp->lineno;
michael@0 676 uint32_t columnNumber = reportp->column;
michael@0 677
michael@0 678 RootedString stack(cx, ComputeStackString(cx));
michael@0 679 if (!stack)
michael@0 680 return cx->isExceptionPending();
michael@0 681
michael@0 682 js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
michael@0 683 if (!report)
michael@0 684 return cx->isExceptionPending();
michael@0 685
michael@0 686 RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
michael@0 687 lineNumber, columnNumber, &report, messageStr));
michael@0 688 if (!errObject)
michael@0 689 return cx->isExceptionPending();
michael@0 690
michael@0 691 // Throw it.
michael@0 692 RootedValue errValue(cx, ObjectValue(*errObject));
michael@0 693 JS_SetPendingException(cx, errValue);
michael@0 694
michael@0 695 // Flag the error report passed in to indicate an exception was raised.
michael@0 696 reportp->flags |= JSREPORT_EXCEPTION;
michael@0 697 return true;
michael@0 698 }
michael@0 699
michael@0 700 static bool
michael@0 701 IsDuckTypedErrorObject(JSContext *cx, HandleObject exnObject, const char **filename_strp)
michael@0 702 {
michael@0 703 bool found;
michael@0 704 if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
michael@0 705 return false;
michael@0 706
michael@0 707 const char *filename_str = *filename_strp;
michael@0 708 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
michael@0 709 /* DOMException duck quacks "filename" (all lowercase) */
michael@0 710 filename_str = "filename";
michael@0 711 if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found)
michael@0 712 return false;
michael@0 713 }
michael@0 714
michael@0 715 if (!JS_HasProperty(cx, exnObject, js_lineNumber_str, &found) || !found)
michael@0 716 return false;
michael@0 717
michael@0 718 *filename_strp = filename_str;
michael@0 719 return true;
michael@0 720 }
michael@0 721
michael@0 722 JS_FRIEND_API(JSString *)
michael@0 723 js::ErrorReportToString(JSContext *cx, JSErrorReport *reportp)
michael@0 724 {
michael@0 725 JSExnType type = static_cast<JSExnType>(reportp->exnType);
michael@0 726 RootedString str(cx, cx->runtime()->emptyString);
michael@0 727 if (type != JSEXN_NONE)
michael@0 728 str = ClassName(GetExceptionProtoKey(type), cx);
michael@0 729 RootedString toAppend(cx, JS_NewUCStringCopyN(cx, MOZ_UTF16(": "), 2));
michael@0 730 if (!str || !toAppend)
michael@0 731 return nullptr;
michael@0 732 str = ConcatStrings<CanGC>(cx, str, toAppend);
michael@0 733 if (!str)
michael@0 734 return nullptr;
michael@0 735 toAppend = JS_NewUCStringCopyZ(cx, reportp->ucmessage);
michael@0 736 if (toAppend)
michael@0 737 str = ConcatStrings<CanGC>(cx, str, toAppend);
michael@0 738 return str;
michael@0 739 }
michael@0 740
michael@0 741 bool
michael@0 742 js_ReportUncaughtException(JSContext *cx)
michael@0 743 {
michael@0 744 if (!cx->isExceptionPending())
michael@0 745 return true;
michael@0 746
michael@0 747 RootedValue exn(cx);
michael@0 748 if (!cx->getPendingException(&exn))
michael@0 749 return false;
michael@0 750
michael@0 751 /*
michael@0 752 * Because ToString below could error and an exception object could become
michael@0 753 * unrooted, we must root exnObject. Later, if exnObject is non-null, we
michael@0 754 * need to root other intermediates, so allocate an operand stack segment
michael@0 755 * to protect all of these values.
michael@0 756 */
michael@0 757 RootedObject exnObject(cx);
michael@0 758 if (JSVAL_IS_PRIMITIVE(exn)) {
michael@0 759 exnObject = nullptr;
michael@0 760 } else {
michael@0 761 exnObject = JSVAL_TO_OBJECT(exn);
michael@0 762 }
michael@0 763
michael@0 764 JS_ClearPendingException(cx);
michael@0 765 JSErrorReport *reportp = exnObject ? js_ErrorFromException(cx, exnObject)
michael@0 766 : nullptr;
michael@0 767
michael@0 768 // Be careful not to invoke ToString if we've already successfully extracted
michael@0 769 // an error report, since the exception might be wrapped in a security
michael@0 770 // wrapper, and ToString-ing it might throw.
michael@0 771 RootedString str(cx);
michael@0 772 if (reportp)
michael@0 773 str = ErrorReportToString(cx, reportp);
michael@0 774 else
michael@0 775 str = ToString<CanGC>(cx, exn);
michael@0 776
michael@0 777 JSErrorReport report;
michael@0 778
michael@0 779 // If js_ErrorFromException didn't get us a JSErrorReport, then the object
michael@0 780 // was not an ErrorObject, security-wrapped or otherwise. However, it might
michael@0 781 // still quack like one. Give duck-typing a chance.
michael@0 782 const char *filename_str = js_fileName_str;
michael@0 783 JSAutoByteString filename;
michael@0 784 if (!reportp && exnObject && IsDuckTypedErrorObject(cx, exnObject, &filename_str))
michael@0 785 {
michael@0 786 // Temporary value for pulling properties off of duck-typed objects.
michael@0 787 RootedValue val(cx);
michael@0 788
michael@0 789 RootedString name(cx);
michael@0 790 if (JS_GetProperty(cx, exnObject, js_name_str, &val) && val.isString())
michael@0 791 name = val.toString();
michael@0 792
michael@0 793 RootedString msg(cx);
michael@0 794 if (JS_GetProperty(cx, exnObject, js_message_str, &val) && val.isString())
michael@0 795 msg = val.toString();
michael@0 796
michael@0 797 // If we have the right fields, override the ToString we performed on
michael@0 798 // the exception object above with something built out of its quacks
michael@0 799 // (i.e. as much of |NameQuack: MessageQuack| as we can make).
michael@0 800 //
michael@0 801 // It would be nice to use ErrorReportToString here, but we can't quite
michael@0 802 // do it - mostly because we'd need to figure out what JSExnType |name|
michael@0 803 // corresponds to, which may not be any JSExnType at all.
michael@0 804 if (name && msg) {
michael@0 805 RootedString colon(cx, JS_NewStringCopyZ(cx, ": "));
michael@0 806 if (!colon)
michael@0 807 return false;
michael@0 808 RootedString nameColon(cx, ConcatStrings<CanGC>(cx, name, colon));
michael@0 809 if (!nameColon)
michael@0 810 return false;
michael@0 811 str = ConcatStrings<CanGC>(cx, nameColon, msg);
michael@0 812 if (!str)
michael@0 813 return false;
michael@0 814 } else if (name) {
michael@0 815 str = name;
michael@0 816 } else if (msg) {
michael@0 817 str = msg;
michael@0 818 }
michael@0 819
michael@0 820 if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
michael@0 821 JSString *tmp = ToString<CanGC>(cx, val);
michael@0 822 if (tmp)
michael@0 823 filename.encodeLatin1(cx, tmp);
michael@0 824 }
michael@0 825
michael@0 826 uint32_t lineno;
michael@0 827 if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &val) ||
michael@0 828 !ToUint32(cx, val, &lineno))
michael@0 829 {
michael@0 830 lineno = 0;
michael@0 831 }
michael@0 832
michael@0 833 uint32_t column;
michael@0 834 if (!JS_GetProperty(cx, exnObject, js_columnNumber_str, &val) ||
michael@0 835 !ToUint32(cx, val, &column))
michael@0 836 {
michael@0 837 column = 0;
michael@0 838 }
michael@0 839
michael@0 840 reportp = &report;
michael@0 841 PodZero(&report);
michael@0 842 report.filename = filename.ptr();
michael@0 843 report.lineno = (unsigned) lineno;
michael@0 844 report.exnType = int16_t(JSEXN_NONE);
michael@0 845 report.column = (unsigned) column;
michael@0 846 if (str) {
michael@0 847 // Note that using |str| for |ucmessage| here is kind of wrong,
michael@0 848 // because |str| is supposed to be of the format
michael@0 849 // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
michael@0 850 // correspond to |ErrorMessage|. But this is what we've historically
michael@0 851 // done for duck-typed error objects.
michael@0 852 //
michael@0 853 // If only this stuff could get specced one day...
michael@0 854 if (JSFlatString *flat = str->ensureFlat(cx))
michael@0 855 report.ucmessage = flat->chars();
michael@0 856 }
michael@0 857 }
michael@0 858
michael@0 859 JSAutoByteString bytesStorage;
michael@0 860 const char *bytes = nullptr;
michael@0 861 if (str)
michael@0 862 bytes = bytesStorage.encodeLatin1(cx, str);
michael@0 863 if (!bytes)
michael@0 864 bytes = "unknown (can't convert to string)";
michael@0 865
michael@0 866 if (!reportp) {
michael@0 867 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
michael@0 868 JSMSG_UNCAUGHT_EXCEPTION, bytes);
michael@0 869 } else {
michael@0 870 /* Flag the error as an exception. */
michael@0 871 reportp->flags |= JSREPORT_EXCEPTION;
michael@0 872
michael@0 873 /* Pass the exception object. */
michael@0 874 JS_SetPendingException(cx, exn);
michael@0 875 CallErrorReporter(cx, bytes, reportp);
michael@0 876 }
michael@0 877
michael@0 878 JS_ClearPendingException(cx);
michael@0 879 return true;
michael@0 880 }
michael@0 881
michael@0 882 JSObject *
michael@0 883 js_CopyErrorObject(JSContext *cx, Handle<ErrorObject*> err, HandleObject scope)
michael@0 884 {
michael@0 885 assertSameCompartment(cx, scope);
michael@0 886
michael@0 887 js::ScopedJSFreePtr<JSErrorReport> copyReport;
michael@0 888 if (JSErrorReport *errorReport = err->getErrorReport()) {
michael@0 889 copyReport = CopyErrorReport(cx, errorReport);
michael@0 890 if (!copyReport)
michael@0 891 return nullptr;
michael@0 892 }
michael@0 893
michael@0 894 RootedString message(cx, err->getMessage());
michael@0 895 if (message && !cx->compartment()->wrap(cx, message.address()))
michael@0 896 return nullptr;
michael@0 897 RootedString fileName(cx, err->fileName(cx));
michael@0 898 if (!cx->compartment()->wrap(cx, fileName.address()))
michael@0 899 return nullptr;
michael@0 900 RootedString stack(cx, err->stack(cx));
michael@0 901 if (!cx->compartment()->wrap(cx, stack.address()))
michael@0 902 return nullptr;
michael@0 903 uint32_t lineNumber = err->lineNumber();
michael@0 904 uint32_t columnNumber = err->columnNumber();
michael@0 905 JSExnType errorType = err->type();
michael@0 906
michael@0 907 // Create the Error object.
michael@0 908 return ErrorObject::create(cx, errorType, stack, fileName,
michael@0 909 lineNumber, columnNumber, &copyReport, message);
michael@0 910 }
michael@0 911
michael@0 912 JS_PUBLIC_API(bool)
michael@0 913 JS::CreateTypeError(JSContext *cx, HandleString stack, HandleString fileName,
michael@0 914 uint32_t lineNumber, uint32_t columnNumber, JSErrorReport *report,
michael@0 915 HandleString message, MutableHandleValue rval)
michael@0 916 {
michael@0 917 assertSameCompartment(cx, stack, fileName, message);
michael@0 918 js::ScopedJSFreePtr<JSErrorReport> rep;
michael@0 919 if (report)
michael@0 920 rep = CopyErrorReport(cx, report);
michael@0 921
michael@0 922 RootedObject obj(cx,
michael@0 923 js::ErrorObject::create(cx, JSEXN_TYPEERR, stack, fileName,
michael@0 924 lineNumber, columnNumber, &rep, message));
michael@0 925 if (!obj)
michael@0 926 return false;
michael@0 927
michael@0 928 rval.setObject(*obj);
michael@0 929 return true;
michael@0 930 }

mercurial