summaryrefslogtreecommitdiff
path: root/js/src/vm/ErrorObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/ErrorObject.cpp')
-rw-r--r--js/src/vm/ErrorObject.cpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp
index 8976d81d27..192b0758db 100644
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -12,6 +12,7 @@
#include "js/CallArgs.h"
#include "js/CharacterEncoding.h"
+#include "vm/StringBuffer.h"
#include "vm/GlobalObject.h"
#include "vm/String.h"
@@ -23,6 +24,283 @@
using namespace js;
+#define IMPLEMENT_ERROR_PROTO_CLASS(name) \
+ { \
+ js_Object_str, \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
+ JS_NULL_CLASS_OPS, \
+ &ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
+ }
+
+const Class
+ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
+ IMPLEMENT_ERROR_PROTO_CLASS(Error),
+
+ IMPLEMENT_ERROR_PROTO_CLASS(InternalError),
+ IMPLEMENT_ERROR_PROTO_CLASS(EvalError),
+ IMPLEMENT_ERROR_PROTO_CLASS(RangeError),
+ IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError),
+ IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError),
+ IMPLEMENT_ERROR_PROTO_CLASS(TypeError),
+ IMPLEMENT_ERROR_PROTO_CLASS(URIError),
+
+ IMPLEMENT_ERROR_PROTO_CLASS(DebuggeeWouldRun),
+ IMPLEMENT_ERROR_PROTO_CLASS(CompileError),
+ IMPLEMENT_ERROR_PROTO_CLASS(RuntimeError)
+};
+
+static bool
+exn_toSource(JSContext* cx, unsigned argc, Value* vp);
+
+static const JSFunctionSpec error_methods[] = {
+#if JS_HAS_TOSOURCE
+ JS_FN(js_toSource_str, exn_toSource, 0, 0),
+#endif
+ JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0),
+ JS_FS_END
+};
+
+static const JSPropertySpec error_properties[] = {
+ JS_STRING_PS("message", "", 0),
+ JS_STRING_PS("name", "Error", 0),
+ // Only Error.prototype has .stack!
+ JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0),
+ JS_PS_END
+};
+
+#define IMPLEMENT_ERROR_PROPERTIES(name) \
+ { \
+ JS_STRING_PS("message", "", 0), \
+ JS_STRING_PS("name", #name, 0), \
+ JS_PS_END \
+ }
+
+static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = {
+ IMPLEMENT_ERROR_PROPERTIES(InternalError),
+ IMPLEMENT_ERROR_PROPERTIES(EvalError),
+ IMPLEMENT_ERROR_PROPERTIES(RangeError),
+ IMPLEMENT_ERROR_PROPERTIES(ReferenceError),
+ IMPLEMENT_ERROR_PROPERTIES(SyntaxError),
+ IMPLEMENT_ERROR_PROPERTIES(TypeError),
+ IMPLEMENT_ERROR_PROPERTIES(URIError),
+ IMPLEMENT_ERROR_PROPERTIES(DebuggeeWouldRun),
+ IMPLEMENT_ERROR_PROPERTIES(CompileError),
+ IMPLEMENT_ERROR_PROPERTIES(RuntimeError)
+};
+
+#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
+ { \
+ ErrorObject::createConstructor, \
+ ErrorObject::createProto, \
+ nullptr, \
+ nullptr, \
+ nullptr, \
+ other_error_properties[JSProto_##name - JSProto_Error - 1], \
+ nullptr, \
+ JSProto_Error \
+ }
+
+#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
+ { \
+ ErrorObject::createConstructor, \
+ ErrorObject::createProto, \
+ nullptr, \
+ nullptr, \
+ nullptr, \
+ other_error_properties[JSProto_##name - JSProto_Error - 1], \
+ nullptr, \
+ JSProto_Error | ClassSpec::DontDefineConstructor \
+ }
+
+const ClassSpec
+ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
+ {
+ ErrorObject::createConstructor,
+ ErrorObject::createProto,
+ nullptr,
+ nullptr,
+ error_methods,
+ error_properties
+ },
+
+ IMPLEMENT_NATIVE_ERROR_SPEC(InternalError),
+ IMPLEMENT_NATIVE_ERROR_SPEC(EvalError),
+ IMPLEMENT_NATIVE_ERROR_SPEC(RangeError),
+ IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError),
+ IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError),
+ IMPLEMENT_NATIVE_ERROR_SPEC(TypeError),
+ IMPLEMENT_NATIVE_ERROR_SPEC(URIError),
+
+ IMPLEMENT_NONGLOBAL_ERROR_SPEC(DebuggeeWouldRun),
+ IMPLEMENT_NONGLOBAL_ERROR_SPEC(CompileError),
+ IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError)
+};
+
+#define IMPLEMENT_ERROR_CLASS(name) \
+ { \
+ js_Error_str, /* yes, really */ \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
+ JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \
+ JSCLASS_BACKGROUND_FINALIZE, \
+ &ErrorObjectClassOps, \
+ &ErrorObject::classSpecs[JSProto_##name - JSProto_Error ] \
+ }
+
+static void
+exn_finalize(FreeOp* fop, JSObject* obj);
+
+static const ClassOps ErrorObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ exn_finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ nullptr, /* trace */
+};
+
+const Class
+ErrorObject::classes[JSEXN_ERROR_LIMIT] = {
+ IMPLEMENT_ERROR_CLASS(Error),
+ IMPLEMENT_ERROR_CLASS(InternalError),
+ IMPLEMENT_ERROR_CLASS(EvalError),
+ IMPLEMENT_ERROR_CLASS(RangeError),
+ IMPLEMENT_ERROR_CLASS(ReferenceError),
+ IMPLEMENT_ERROR_CLASS(SyntaxError),
+ IMPLEMENT_ERROR_CLASS(TypeError),
+ IMPLEMENT_ERROR_CLASS(URIError),
+ // These Error subclasses are not accessible via the global object:
+ IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun),
+ IMPLEMENT_ERROR_CLASS(CompileError),
+ IMPLEMENT_ERROR_CLASS(RuntimeError)
+};
+
+static void
+exn_finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(fop->maybeOffMainThread());
+ if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport())
+ fop->delete_(report);
+}
+
+bool
+Error(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
+ RootedObject proto(cx);
+ if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+ return false;
+
+ /* Compute the error message, if any. */
+ RootedString message(cx, nullptr);
+ if (args.hasDefined(0)) {
+ message = ToString<CanGC>(cx, args[0]);
+ if (!message)
+ return false;
+ }
+
+ /* Find the scripted caller, but only ones we're allowed to know about. */
+ NonBuiltinFrameIter iter(cx, cx->compartment()->principals());
+
+ /* Set the 'fileName' property. */
+ RootedString fileName(cx);
+ if (args.length() > 1) {
+ fileName = ToString<CanGC>(cx, args[1]);
+ } else {
+ fileName = cx->runtime()->emptyString;
+ if (!iter.done()) {
+ if (const char* cfilename = iter.filename())
+ fileName = JS_NewStringCopyZ(cx, cfilename);
+ }
+ }
+ if (!fileName)
+ return false;
+
+ /* Set the 'lineNumber' property. */
+ uint32_t lineNumber, columnNumber = 0;
+ if (args.length() > 2) {
+ if (!ToUint32(cx, args[2], &lineNumber))
+ return false;
+ } else {
+ lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
+ // XXX: Make the column 1-based as in other browsers, instead of 0-based
+ // which is how SpiderMonkey stores it internally. This will be
+ // unnecessary once bug 1144340 is fixed.
+ ++columnNumber;
+ }
+
+ RootedObject stack(cx);
+ if (!CaptureStack(cx, &stack))
+ return false;
+
+ /*
+ * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
+ * called as functions, without operator new. But as we do not give
+ * each constructor a distinct JSClass, we must get the exception type
+ * ourselves.
+ */
+ JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
+
+ RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
+ lineNumber, columnNumber, nullptr, message, proto));
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/* static */ JSObject*
+ErrorObject::createProto(JSContext* cx, JSProtoKey key)
+{
+ JSExnType type = ExnTypeFromProtoKey(key);
+
+ if (type == JSEXN_ERR) {
+ return GlobalObject::createBlankPrototype(cx, cx->global(),
+ &ErrorObject::protoClasses[JSEXN_ERR]);
+ }
+
+ RootedObject protoProto(cx, GlobalObject::getOrCreateErrorPrototype(cx, cx->global()));
+ if (!protoProto)
+ return nullptr;
+
+ return GlobalObject::createBlankPrototypeInheriting(cx, cx->global(),
+ &ErrorObject::protoClasses[type],
+ protoProto);
+}
+
+/* static */ JSObject*
+ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
+{
+ JSExnType type = ExnTypeFromProtoKey(key);
+ RootedObject ctor(cx);
+
+ if (type == JSEXN_ERR) {
+ ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key);
+ } else {
+ RootedFunction proto(cx, GlobalObject::getOrCreateErrorConstructor(cx, cx->global()));
+ if (!proto)
+ return nullptr;
+
+ ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr,
+ ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED,
+ SingletonObject);
+ }
+
+ if (!ctor)
+ return nullptr;
+
+ ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(type));
+ return ctor;
+}
+
/* static */ Shape*
js::ErrorObject::assignInitialShape(ExclusiveContext* cx, Handle<ErrorObject*> obj)
{
@@ -280,3 +558,81 @@ js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args)
return DefineProperty(cx, thisObj, cx->names().stack, val);
}
+
+/*
+ * Return a string that may eval to something similar to the original object.
+ */
+static bool
+exn_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_CHECK_RECURSION(cx, return false);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, ToObject(cx, args.thisv()));
+ if (!obj)
+ return false;
+
+ RootedValue nameVal(cx);
+ RootedString name(cx);
+ if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) ||
+ !(name = ToString<CanGC>(cx, nameVal)))
+ {
+ return false;
+ }
+
+ RootedValue messageVal(cx);
+ RootedString message(cx);
+ if (!GetProperty(cx, obj, obj, cx->names().message, &messageVal) ||
+ !(message = ValueToSource(cx, messageVal)))
+ {
+ return false;
+ }
+
+ RootedValue filenameVal(cx);
+ RootedString filename(cx);
+ if (!GetProperty(cx, obj, obj, cx->names().fileName, &filenameVal) ||
+ !(filename = ValueToSource(cx, filenameVal)))
+ {
+ return false;
+ }
+
+ RootedValue linenoVal(cx);
+ uint32_t lineno;
+ if (!GetProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) ||
+ !ToUint32(cx, linenoVal, &lineno))
+ {
+ return false;
+ }
+
+ StringBuffer sb(cx);
+ if (!sb.append("(new ") || !sb.append(name) || !sb.append("("))
+ return false;
+
+ if (!sb.append(message))
+ return false;
+
+ if (!filename->empty()) {
+ if (!sb.append(", ") || !sb.append(filename))
+ return false;
+ }
+ if (lineno != 0) {
+ /* We have a line, but no filename, add empty string */
+ if (filename->empty() && !sb.append(", \"\""))
+ return false;
+
+ JSString* linenumber = ToString<CanGC>(cx, linenoVal);
+ if (!linenumber)
+ return false;
+ if (!sb.append(", ") || !sb.append(linenumber))
+ return false;
+ }
+
+ if (!sb.append("))"))
+ return false;
+
+ JSString* str = sb.finishString();
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}