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