Bug 891107 - Part 1: Show information about value, type, function, and argument number in type conversion error messages in js-ctypes. r=jorendorff
☠☠ backed out by a1c096248e7f ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Wed, 22 Apr 2015 18:26:13 +0900
changeset 240413 b2b956ba0acd53acb035eae3383a9a75c3389dfe
parent 240412 7eee0cdd0febcb70cbfd17b955be317701dd8e58
child 240414 951ec7d134c26e325ff92e021a8cc742d4d61b5f
push id58825
push userarai_a@mac.com
push dateWed, 22 Apr 2015 09:32:30 +0000
treeherdermozilla-inbound@467559ddc08f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs891107
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 891107 - Part 1: Show information about value, type, function, and argument number in type conversion error messages in js-ctypes. r=jorendorff
js/src/ctypes/CTypes.cpp
js/src/ctypes/CTypes.h
js/src/ctypes/Library.cpp
js/src/ctypes/ctypes.msg
js/src/jit-test/lib/asserts.js
js/src/jit-test/tests/ctypes/conversion-array.js
js/src/jit-test/tests/ctypes/conversion-error.js
js/src/jit-test/tests/ctypes/conversion-finalizer.js
js/src/jit-test/tests/ctypes/conversion-function.js
js/src/jit-test/tests/ctypes/conversion-int64.js
js/src/jit-test/tests/ctypes/conversion-native-function.js
js/src/jit-test/tests/ctypes/conversion-pointer.js
js/src/jit-test/tests/ctypes/conversion-primitive.js
js/src/jit-test/tests/ctypes/conversion-struct.js
js/src/jit-test/tests/ctypes/conversion-to-primitive.js
js/src/jsexn.cpp
js/src/jsexn.h
toolkit/components/ctypes/tests/unit/test_jsctypes.js
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -27,24 +27,27 @@
 
 #if defined(XP_UNIX)
 #include <errno.h>
 #elif defined(XP_WIN)
 #include <windows.h>
 #endif
 
 #include "jscntxt.h"
+#include "jsexn.h"
 #include "jsfun.h"
 #include "jsnum.h"
 #include "jsprf.h"
 
 #include "builtin/TypedObject.h"
 #include "ctypes/Library.h"
 #include "gc/Zone.h"
 
+#include "jsatominlines.h"
+
 using namespace std;
 using mozilla::NumericLimits;
 
 using JS::AutoCheckCannotGC;
 
 namespace js {
 namespace ctypes {
 
@@ -871,31 +874,577 @@ static const JSErrorFormatString ErrorFo
 static const JSErrorFormatString*
 GetErrorMessage(void* userRef, const unsigned errorNumber)
 {
   if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT)
     return &ErrorFormatString[errorNumber];
   return nullptr;
 }
 
+static const char*
+EncodeLatin1(JSContext* cx, AutoString& str, JSAutoByteString& bytes)
+{
+  return bytes.encodeLatin1(cx, NewUCString(cx, str));
+}
+
+static const char*
+CTypesToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes)
+{
+  if (val.isObject() &&
+      (CType::IsCType(&val.toObject()) || CData::IsCData(&val.toObject()))) {
+    RootedString str(cx, JS_ValueToSource(cx, val));
+    return bytes.encodeLatin1(cx, str);
+  }
+  return ValueToSourceForError(cx, val, bytes);
+}
+
+static void
+BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj,
+                              HandleString nameStr, unsigned ptrCount,
+                              AutoString& source);
+
+static void
+BuildCStyleTypeSource(JSContext* cx, JSObject* typeObj_, AutoString& source)
+{
+  RootedObject typeObj(cx, typeObj_);
+
+  MOZ_ASSERT(CType::IsCType(typeObj));
+
+  switch (CType::GetTypeCode(typeObj)) {
+#define BUILD_SOURCE(name, fromType, ffiType)                                  \
+  case TYPE_##name:                                                            \
+    AppendString(source, #name);                                               \
+    break;
+  CTYPES_FOR_EACH_TYPE(BUILD_SOURCE)
+#undef BUILD_SOURCE
+  case TYPE_void_t:
+    AppendString(source, "void");
+    break;
+  case TYPE_pointer: {
+    unsigned ptrCount = 0;
+    TypeCode type;
+    RootedObject baseTypeObj(cx, typeObj);
+    do {
+      baseTypeObj = PointerType::GetBaseType(baseTypeObj);
+      ptrCount++;
+      type = CType::GetTypeCode(baseTypeObj);
+    } while (type == TYPE_pointer || type == TYPE_array);
+    if (type == TYPE_function) {
+      BuildCStyleFunctionTypeSource(cx, baseTypeObj, NullPtr(), ptrCount,
+                                    source);
+      break;
+    }
+    BuildCStyleTypeSource(cx, baseTypeObj, source);
+    AppendChars(source, '*', ptrCount);
+    break;
+  }
+  case TYPE_struct: {
+    RootedString name(cx, CType::GetName(cx, typeObj));
+    AppendString(source, "struct ");
+    AppendString(source, name);
+    break;
+  }
+  case TYPE_function:
+    BuildCStyleFunctionTypeSource(cx, typeObj, NullPtr(), 0, source);
+    break;
+  case TYPE_array:
+    MOZ_CRASH("TYPE_array shouldn't appear in function type");
+  }
+}
+
+static void
+BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj,
+                              HandleString nameStr, unsigned ptrCount,
+                              AutoString& source)
+{
+  MOZ_ASSERT(CType::IsCType(typeObj));
+
+  FunctionInfo* fninfo = FunctionType::GetFunctionInfo(typeObj);
+  BuildCStyleTypeSource(cx, fninfo->mReturnType, source);
+  AppendString(source, " ");
+  if (nameStr) {
+    MOZ_ASSERT(ptrCount == 0);
+    AppendString(source, nameStr);
+  } else if (ptrCount) {
+    AppendString(source, "(");
+    AppendChars(source, '*', ptrCount);
+    AppendString(source, ")");
+  }
+  AppendString(source, "(");
+  if (fninfo->mArgTypes.length() > 0) {
+    for (size_t i = 0; i < fninfo->mArgTypes.length(); ++i) {
+      BuildCStyleTypeSource(cx, fninfo->mArgTypes[i], source);
+      if (i != fninfo->mArgTypes.length() - 1 || fninfo->mIsVariadic) {
+          AppendString(source, ", ");
+      }
+    }
+    if (fninfo->mIsVariadic) {
+      AppendString(source, "...");
+    }
+  }
+  AppendString(source, ")");
+}
+
+static void
+BuildFunctionTypeSource(JSContext* cx, HandleObject funObj, AutoString& source)
+{
+  MOZ_ASSERT(CData::IsCData(funObj) || CType::IsCType(funObj));
+
+  if (CData::IsCData(funObj)) {
+    jsval slot = JS_GetReservedSlot(funObj, SLOT_REFERENT);
+    if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) {
+      slot = JS_GetReservedSlot(funObj, SLOT_FUNNAME);
+      MOZ_ASSERT(!slot.isUndefined());
+      RootedObject typeObj(cx, CData::GetCType(funObj));
+      RootedObject baseTypeObj(cx, PointerType::GetBaseType(typeObj));
+      RootedString nameStr(cx, slot.toString());
+      BuildCStyleFunctionTypeSource(cx, baseTypeObj, nameStr, 0, source);
+      return;
+    }
+  }
+
+  RootedValue funVal(cx, ObjectValue(*funObj));
+  RootedString funcStr(cx, JS_ValueToSource(cx, funVal));
+  if (!funcStr) {
+    JS_ClearPendingException(cx);
+    AppendString(source, "<<error converting function to string>>");
+    return;
+  }
+  AppendString(source, funcStr);
+}
+
+enum class ConversionType {
+  Argument = 0,
+  Construct,
+  Finalizer,
+  Return,
+  Setter
+};
+
+static void
+BuildConversionPosition(JSContext* cx, ConversionType convType,
+                        HandleObject funObj, unsigned argIndex,
+                        AutoString& source)
+{
+  switch (convType) {
+  case ConversionType::Argument: {
+    MOZ_ASSERT(funObj);
+
+    AppendString(source, " at argument ");
+    AppendUInt(source, argIndex + 1);
+    AppendString(source, " of ");
+    BuildFunctionTypeSource(cx, funObj, source);
+    break;
+  }
+  case ConversionType::Finalizer:
+    MOZ_ASSERT(funObj);
+
+    AppendString(source, " at argument 1 of ");
+    BuildFunctionTypeSource(cx, funObj, source);
+    break;
+  case ConversionType::Return:
+    MOZ_ASSERT(funObj);
+
+    AppendString(source, " at the return value of ");
+    BuildFunctionTypeSource(cx, funObj, source);
+    break;
+  default:
+    MOZ_ASSERT(!funObj);
+    break;
+  }
+}
+
+static JSFlatString*
+GetFieldName(HandleObject structObj, unsigned fieldIndex)
+{
+  const FieldInfoHash* fields = StructType::GetFieldInfo(structObj);
+  for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
+    if (r.front().value().mIndex == fieldIndex) {
+      return (&r.front())->key();
+    }
+  }
+  return nullptr;
+}
+
+static void
+BuildTypeSource(JSContext* cx, JSObject* typeObj_, bool makeShort,
+                AutoString& result);
+
+static bool
+ConvError(JSContext* cx, const char* expectedStr, HandleValue actual,
+          ConversionType convType,
+          HandleObject funObj = NullPtr(), unsigned argIndex = 0,
+          HandleObject arrObj = NullPtr(), unsigned arrIndex = 0)
+{
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  if (arrObj) {
+    MOZ_ASSERT(CType::IsCType(arrObj));
+
+    switch (CType::GetTypeCode(arrObj)) {
+    case TYPE_array: {
+      MOZ_ASSERT(!funObj);
+
+      char indexStr[16];
+      JS_snprintf(indexStr, 16, "%u", arrIndex);
+
+      AutoString arrSource;
+      JSAutoByteString arrBytes;
+      BuildTypeSource(cx, arrObj, true, arrSource);
+      const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes);
+      if (!arrStr)
+        return false;
+
+      JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                           CTYPESMSG_CONV_ERROR_ARRAY,
+                           valStr, indexStr, arrStr);
+      break;
+    }
+    case TYPE_struct: {
+      JSFlatString* name = GetFieldName(arrObj, arrIndex);
+      MOZ_ASSERT(name);
+      JSAutoByteString nameBytes;
+      const char* nameStr = nameBytes.encodeLatin1(cx, name);
+      if (!nameStr)
+        return false;
+
+      AutoString structSource;
+      JSAutoByteString structBytes;
+      BuildTypeSource(cx, arrObj, true, structSource);
+      const char* structStr = EncodeLatin1(cx, structSource, structBytes);
+      if (!structStr)
+        return false;
+
+      JSAutoByteString posBytes;
+      const char* posStr;
+      if (funObj) {
+        AutoString posSource;
+        BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
+        posStr = EncodeLatin1(cx, posSource, posBytes);
+        if (!posStr)
+          return false;
+      } else {
+        posStr = "";
+      }
+
+      JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                           CTYPESMSG_CONV_ERROR_STRUCT,
+                           valStr, nameStr, expectedStr, structStr, posStr);
+      break;
+    }
+    default:
+      MOZ_CRASH("invalid arrObj value");
+    }
+    return false;
+  }
+
+  switch (convType) {
+  case ConversionType::Argument: {
+    MOZ_ASSERT(funObj);
+
+    char indexStr[16];
+    JS_snprintf(indexStr, 16, "%u", argIndex + 1);
+
+    AutoString funSource;
+    JSAutoByteString funBytes;
+    BuildFunctionTypeSource(cx, funObj, funSource);
+    const char* funStr = EncodeLatin1(cx, funSource, funBytes);
+    if (!funStr)
+      return false;
+
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                         CTYPESMSG_CONV_ERROR_ARG,
+                         valStr, indexStr, funStr);
+    break;
+  }
+  case ConversionType::Finalizer: {
+    MOZ_ASSERT(funObj);
+
+    AutoString funSource;
+    JSAutoByteString funBytes;
+    BuildFunctionTypeSource(cx, funObj, funSource);
+    const char* funStr = EncodeLatin1(cx, funSource, funBytes);
+    if (!funStr)
+      return false;
+
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                         CTYPESMSG_CONV_ERROR_FIN, valStr, funStr);
+    break;
+  }
+  case ConversionType::Return: {
+    MOZ_ASSERT(funObj);
+
+    AutoString funSource;
+    JSAutoByteString funBytes;
+    BuildFunctionTypeSource(cx, funObj, funSource);
+    const char* funStr = EncodeLatin1(cx, funSource, funBytes);
+    if (!funStr)
+      return false;
+
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                         CTYPESMSG_CONV_ERROR_RET, valStr, funStr);
+    break;
+  }
+  case ConversionType::Setter:
+  case ConversionType::Construct:
+    MOZ_ASSERT(!funObj);
+
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                         CTYPESMSG_CONV_ERROR_SET, valStr, expectedStr);
+    break;
+  }
+
+  return false;
+}
+
+static bool
+ConvError(JSContext* cx, HandleObject expectedType, HandleValue actual,
+          ConversionType convType,
+          HandleObject funObj = NullPtr(), unsigned argIndex = 0,
+          HandleObject arrObj = NullPtr(), unsigned arrIndex = 0)
+{
+  MOZ_ASSERT(CType::IsCType(expectedType));
+
+  AutoString expectedSource;
+  JSAutoByteString expectedBytes;
+  BuildTypeSource(cx, expectedType, true, expectedSource);
+  const char* expectedStr = EncodeLatin1(cx, expectedSource, expectedBytes);
+  if (!expectedStr)
+    return false;
+
+  return ConvError(cx, expectedStr, actual, convType, funObj, argIndex,
+                   arrObj, arrIndex);
+}
+
+static bool
+ArgumentConvError(JSContext* cx, HandleValue actual, const char* funStr,
+                  unsigned argIndex)
+{
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  char indexStr[16];
+  JS_snprintf(indexStr, 16, "%u", argIndex + 1);
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_CONV_ERROR_ARG, valStr, indexStr, funStr);
+  return false;
+}
+
+static bool
+ArrayLengthMismatch(JSContext* cx, unsigned expectedLength, HandleObject arrObj,
+                    unsigned actualLength, HandleValue actual,
+                    ConversionType convType)
+{
+  MOZ_ASSERT(arrObj && CType::IsCType(arrObj));
+
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  char expectedLengthStr[16];
+  JS_snprintf(expectedLengthStr, 16, "%u", expectedLength);
+  char actualLengthStr[16];
+  JS_snprintf(actualLengthStr, 16, "%u", actualLength);
+
+  AutoString arrSource;
+  JSAutoByteString arrBytes;
+  BuildTypeSource(cx, arrObj, true, arrSource);
+  const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes);
+  if (!arrStr)
+    return false;
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_ARRAY_MISMATCH,
+                       valStr, arrStr, expectedLengthStr, actualLengthStr);
+  return false;
+}
+
+static bool
+ArrayLengthOverflow(JSContext* cx, unsigned expectedLength, HandleObject arrObj,
+                    unsigned actualLength, HandleValue actual,
+                    ConversionType convType)
+{
+  MOZ_ASSERT(arrObj && CType::IsCType(arrObj));
+
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  char expectedLengthStr[16];
+  JS_snprintf(expectedLengthStr, 16, "%u", expectedLength);
+  char actualLengthStr[16];
+  JS_snprintf(actualLengthStr, 16, "%u", actualLength);
+
+  AutoString arrSource;
+  JSAutoByteString arrBytes;
+  BuildTypeSource(cx, arrObj, true, arrSource);
+  const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes);
+  if (!arrStr)
+    return false;
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_ARRAY_OVERFLOW,
+                       valStr, arrStr, expectedLengthStr, actualLengthStr);
+  return false;
+}
+
+static bool
+EmptyFinalizerError(JSContext* cx, ConversionType convType,
+                    HandleObject funObj = NullPtr(), unsigned argIndex = 0)
+{
+  JSAutoByteString posBytes;
+  const char* posStr;
+  if (funObj) {
+    AutoString posSource;
+    BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
+    posStr = EncodeLatin1(cx, posSource, posBytes);
+    if (!posStr)
+      return false;
+  } else {
+    posStr = "";
+  }
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_EMPTY_FIN, posStr);
+  return false;
+}
+
+static bool
+FieldCountMismatch(JSContext* cx,
+                   unsigned expectedCount, HandleObject structObj,
+                   unsigned actualCount, HandleValue actual,
+                   ConversionType convType,
+                   HandleObject funObj = NullPtr(), unsigned argIndex = 0)
+{
+  MOZ_ASSERT(structObj && CType::IsCType(structObj));
+
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  AutoString structSource;
+  JSAutoByteString structBytes;
+  BuildTypeSource(cx, structObj, true, structSource);
+  const char* structStr = EncodeLatin1(cx, structSource, structBytes);
+  if (!structStr)
+    return false;
+
+  char expectedCountStr[16];
+  JS_snprintf(expectedCountStr, 16, "%u", expectedCount);
+  char actualCountStr[16];
+  JS_snprintf(actualCountStr, 16, "%u", actualCount);
+
+  JSAutoByteString posBytes;
+  const char* posStr;
+  if (funObj) {
+    AutoString posSource;
+    BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
+    posStr = EncodeLatin1(cx, posSource, posBytes);
+    if (!posStr)
+      return false;
+  } else {
+    posStr = "";
+  }
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_FIELD_MISMATCH,
+                       valStr, structStr, expectedCountStr, actualCountStr,
+                       posStr);
+  return false;
+}
+
+static bool
+FinalizerSizeError(JSContext* cx, HandleObject funObj, HandleValue actual)
+{
+  MOZ_ASSERT(CType::IsCType(funObj));
+
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  AutoString funSource;
+  JSAutoByteString funBytes;
+  BuildFunctionTypeSource(cx, funObj, funSource);
+  const char* funStr = EncodeLatin1(cx, funSource, funBytes);
+  if (!funStr)
+    return false;
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_FIN_SIZE_ERROR, funStr, valStr);
+  return false;
+}
+
+static bool
+NonPrimitiveError(JSContext* cx, HandleObject typeObj)
+{
+  MOZ_ASSERT(CType::IsCType(typeObj));
+
+  AutoString typeSource;
+  JSAutoByteString typeBytes;
+  BuildTypeSource(cx, typeObj, true, typeSource);
+  const char* typeStr = EncodeLatin1(cx, typeSource, typeBytes);
+  if (!typeStr)
+    return false;
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_NON_PRIMITIVE, typeStr);
+  return false;
+}
+
+static bool
+PropNameNonStringError(JSContext* cx, HandleId id, HandleValue actual,
+                       ConversionType convType,
+                       HandleObject funObj = NullPtr(), unsigned argIndex = 0)
+{
+  JSAutoByteString valBytes;
+  const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
+  if (!valStr)
+    return false;
+
+  JSAutoByteString idBytes;
+  RootedValue idVal(cx, IdToValue(id));
+  const char* propStr = CTypesToSourceForError(cx, idVal, idBytes);
+  if (!propStr)
+    return false;
+
+  JSAutoByteString posBytes;
+  const char* posStr;
+  if (funObj) {
+    AutoString posSource;
+    BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
+    posStr = EncodeLatin1(cx, posSource, posBytes);
+    if (!posStr)
+      return false;
+  } else {
+    posStr = "";
+  }
+
+  JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                       CTYPESMSG_PROP_NONSTRING, propStr, valStr, posStr);
+  return false;
+}
+
 static bool
 TypeError(JSContext* cx, const char* expected, HandleValue actual)
 {
-  JSString* str = JS_ValueToSource(cx, actual);
   JSAutoByteString bytes;
-
-  const char* src;
-  if (str) {
-    src = bytes.encodeLatin1(cx, str);
-    if (!src)
-      return false;
-  } else {
-    JS_ClearPendingException(cx);
-    src = "<<error converting value to string>>";
-  }
+  const char* src = CTypesToSourceForError(cx, actual, bytes);
+  if (!src)
+    return false;
+
   JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
                        CTYPESMSG_TYPE_ERROR, expected, src);
   return false;
 }
 
 static JSObject*
 InitCTypeClass(JSContext* cx, HandleObject ctypesObj)
 {
@@ -2205,18 +2754,17 @@ ConvertToJS(JSContext* cx,
     break;
   }
   case TYPE_pointer:
   case TYPE_array:
   case TYPE_struct: {
     // We're about to create a new CData object to return. If the caller doesn't
     // want this, return early.
     if (wantPrimitive) {
-      JS_ReportError(cx, "cannot convert to primitive value");
-      return false;
+      return NonPrimitiveError(cx, typeObj);
     }
 
     JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult);
     if (!obj)
       return false;
 
     result.setObject(*obj);
     break;
@@ -2271,28 +2819,30 @@ bool CanConvertTypedArrayItemTo(JSObject
 }
 
 // Implicitly convert jsval 'val' to a C binary representation of CType
 // 'targetType', storing the result in 'buffer'. Adequate space must be
 // provided in 'buffer' by the caller. This function generally does minimal
 // coercion between types. There are two cases in which this function is used:
 // 1) The target buffer is internal to a CData object; we simply write data
 //    into it.
-// 2) We are converting an argument for an ffi call, in which case 'isArgument'
-//    will be true. This allows us to handle a special case: if necessary,
-//    we can autoconvert a JS string primitive to a pointer-to-character type.
-//    In this case, ownership of the allocated string is handed off to the
-//    caller; 'freePointer' will be set to indicate this.
+// 2) We are converting an argument for an ffi call, in which case 'convType'
+//    will be 'ConversionType::Argument'. This allows us to handle a special
+//    case: if necessary, we can autoconvert a JS string primitive to a
+//    pointer-to-character type. In this case, ownership of the allocated string
+//    is handed off to the caller; 'freePointer' will be set to indicate this.
 static bool
 ImplicitConvert(JSContext* cx,
                 HandleValue val,
                 JSObject* targetType_,
                 void* buffer,
-                bool isArgument,
-                bool* freePointer)
+                ConversionType convType,
+                bool* freePointer,
+                HandleObject funObj = NullPtr(), unsigned argIndex = 0,
+                HandleObject arrObj = NullPtr(), unsigned arrIndex = 0)
 {
   RootedObject targetType(cx, targetType_);
   MOZ_ASSERT(CType::IsSizeDefined(targetType));
 
   // First, check if val is either a CData object or a CDataFinalizer
   // of type targetType.
   JSObject* sourceData = nullptr;
   JSObject* sourceType = nullptr;
@@ -2314,18 +2864,17 @@ ImplicitConvert(JSContext* cx,
       sourceData = valObj;
       sourceType = CDataFinalizer::GetCType(cx, sourceData);
 
       CDataFinalizer::Private* p = (CDataFinalizer::Private*)
         JS_GetPrivate(sourceData);
 
       if (!p) {
         // We have called |dispose| or |forget| already.
-        JS_ReportError(cx, "Attempting to convert an empty CDataFinalizer");
-        return false;
+        return EmptyFinalizerError(cx, convType, funObj, argIndex);
       }
 
       // If the types are equal, copy the buffer contained within the CData.
       if (CType::TypesEqual(sourceType, targetType)) {
         memmove(buffer, p->cargs, p->cargs_size);
         return true;
       }
     }
@@ -2334,63 +2883,68 @@ ImplicitConvert(JSContext* cx,
   TypeCode targetCode = CType::GetTypeCode(targetType);
 
   switch (targetCode) {
   case TYPE_bool: {
     // Do not implicitly lose bits, but allow the values 0, 1, and -0.
     // Programs can convert explicitly, if needed, using `Boolean(v)` or `!!v`.
     bool result;
     if (!jsvalToBool(cx, val, &result))
-      return TypeError(cx, "boolean", val);
+      return ConvError(cx, "boolean", val, convType, funObj, argIndex,
+                       arrObj, arrIndex);
     *static_cast<bool*>(buffer) = result;
     break;
   }
 #define CHAR16_CASE(name, type, ffiType)                                       \
   case TYPE_##name: {                                                          \
     /* Convert from a 1-character string, regardless of encoding, */           \
     /* or from an integer, provided the result fits in 'type'. */              \
     type result;                                                               \
     if (val.isString()) {                                                      \
       JSString* str = val.toString();                                          \
       if (str->length() != 1)                                                  \
-        return TypeError(cx, #name, val);                                      \
+        return ConvError(cx, #name, val, convType, funObj, argIndex,           \
+                         arrObj, arrIndex);                                    \
       JSLinearString* linear = str->ensureLinear(cx);                          \
       if (!linear)                                                             \
         return false;                                                          \
       result = linear->latin1OrTwoByteChar(0);                                 \
     } else if (!jsvalToInteger(cx, val, &result)) {                            \
-      return TypeError(cx, #name, val);                                        \
+      return ConvError(cx, #name, val, convType, funObj, argIndex,             \
+                       arrObj, arrIndex);                                      \
     }                                                                          \
     *static_cast<type*>(buffer) = result;                                      \
     break;                                                                     \
   }
   CTYPES_FOR_EACH_CHAR16_TYPE(CHAR16_CASE)
 #undef CHAR16_CASE
 #define INTEGRAL_CASE(name, type, ffiType)                                     \
   case TYPE_##name: {                                                          \
     /* Do not implicitly lose bits. */                                         \
     type result;                                                               \
     if (!jsvalToInteger(cx, val, &result))                                     \
-      return TypeError(cx, #name, val);                                        \
+      return ConvError(cx, #name, val, convType, funObj, argIndex,             \
+                       arrObj, arrIndex);                                      \
     *static_cast<type*>(buffer) = result;                                      \
     break;                                                                     \
   }
   CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
   CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE)
   // It's hard to believe ctypes.char16_t("f") should work yet ctypes.char("f")
   // should not.  Ditto for ctypes.{un,}signed_char.  But this is how ctypes
   // has always worked, so preserve these semantics, and don't switch to an
   // algorithm similar to that in DEFINE_CHAR16_TYPE above, just yet.
   CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE)
 #undef INTEGRAL_CASE
 #define FLOAT_CASE(name, type, ffiType)                                        \
   case TYPE_##name: {                                                          \
     type result;                                                               \
     if (!jsvalToFloat(cx, val, &result))                                       \
-      return TypeError(cx, #name, val);                                        \
+      return ConvError(cx, #name, val, convType, funObj, argIndex,             \
+                       arrObj, arrIndex);                                      \
     *static_cast<type*>(buffer) = result;                                      \
     break;                                                                     \
   }
   CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE)
 #undef FLOAT_CASE
   case TYPE_pointer: {
     if (val.isNull()) {
       // Convert to a null pointer.
@@ -2415,17 +2969,17 @@ ImplicitConvert(JSContext* cx,
         // sourceType.elementType.ptr, just like C.
         JSObject* elementType = ArrayType::GetBaseType(sourceType);
         if (voidptrTarget || CType::TypesEqual(baseType, elementType)) {
           *static_cast<void**>(buffer) = sourceBuffer;
           break;
         }
       }
 
-    } else if (isArgument && val.isString()) {
+    } else if (convType == ConversionType::Argument && val.isString()) {
       // Convert the string for the ffi call. This requires allocating space
       // which the caller assumes ownership of.
       // TODO: Extend this so we can safely convert strings at other times also.
       JSString* sourceString = val.toString();
       size_t sourceLength = sourceString->length();
       JSLinearString* sourceLinear = sourceString->ensureLinear(cx);
       if (!sourceLinear)
         return false;
@@ -2469,60 +3023,69 @@ ImplicitConvert(JSContext* cx,
         } else {
             AutoCheckCannotGC nogc;
             mozilla::PodCopy(*char16Buffer, sourceLinear->twoByteChars(nogc), sourceLength);
         }
         (*char16Buffer)[sourceLength] = 0;
         break;
       }
       default:
-        return TypeError(cx, "string pointer", val);
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
       break;
     } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) {
       // Convert ArrayBuffer to pointer without any copy. This is only valid
       // when converting an argument to a function call, as it is possible for
       // the pointer to be invalidated by anything that runs JS code. (It is
       // invalid to invoke JS code from a ctypes function call.)
-      if (!isArgument) {
-        return TypeError(cx, "arraybuffer pointer", val);
+      if (convType != ConversionType::Argument) {
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
       void* ptr;
       {
           JS::AutoCheckCannotGC nogc;
           ptr = JS_GetArrayBufferData(valObj, nogc);
       }
       if (!ptr) {
-        return TypeError(cx, "arraybuffer pointer", val);
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
       *static_cast<void**>(buffer) = ptr;
       break;
     } else if (val.isObject() && JS_IsArrayBufferViewObject(valObj)) {
       // Same as ArrayBuffer, above, though note that this will take the
       // offset of the view into account.
       if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
-        return TypeError(cx, "typed array with the appropriate type", val);
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
-      if (!isArgument) {
-        return TypeError(cx, "typed array pointer", val);
+      if (convType != ConversionType::Argument) {
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
       void* ptr;
       {
           JS::AutoCheckCannotGC nogc;
           ptr = JS_GetArrayBufferViewData(valObj, nogc);
       }
       if (!ptr) {
-        return TypeError(cx, "typed array pointer", val);
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
       *static_cast<void**>(buffer) = ptr;
       break;
     }
-    return TypeError(cx, "pointer", val);
+    return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                     arrObj, arrIndex);
   }
   case TYPE_array: {
+    MOZ_ASSERT(!funObj);
+
     RootedObject baseType(cx, ArrayType::GetBaseType(targetType));
     size_t targetLength = ArrayType::GetLength(targetType);
 
     if (val.isString()) {
       JSString* sourceString = val.toString();
       size_t sourceLength = sourceString->length();
       JSLinearString* sourceLinear = sourceString->ensureLinear(cx);
       if (!sourceLinear)
@@ -2534,35 +3097,37 @@ ImplicitConvert(JSContext* cx,
       case TYPE_unsigned_char: {
         // Convert from UTF-16 or Latin1 to UTF-8.
         size_t nbytes =
           GetDeflatedUTF8StringLength(cx, sourceLinear);
         if (nbytes == (size_t) -1)
           return false;
 
         if (targetLength < nbytes) {
-          JS_ReportError(cx, "ArrayType has insufficient length");
-          return false;
+          MOZ_ASSERT(!funObj);
+          return ArrayLengthOverflow(cx, targetLength, targetType, nbytes, val,
+                                     convType);
         }
 
         char* charBuffer = static_cast<char*>(buffer);
         ASSERT_OK(DeflateStringToUTF8Buffer(cx, sourceLinear, charBuffer,
                                             &nbytes));
 
         if (targetLength > nbytes)
           charBuffer[nbytes] = 0;
 
         break;
       }
       case TYPE_char16_t: {
         // Copy the string data, char16_t for char16_t, including the terminator
         // if there's space.
         if (targetLength < sourceLength) {
-          JS_ReportError(cx, "ArrayType has insufficient length");
-          return false;
+          MOZ_ASSERT(!funObj);
+          return ArrayLengthOverflow(cx, targetLength, targetType,
+                                     sourceLength, val, convType);
         }
 
         char16_t* dest = static_cast<char16_t*>(buffer);
         if (sourceLinear->hasLatin1Chars()) {
             AutoCheckCannotGC nogc;
             CopyAndInflateChars(dest, sourceLinear->latin1Chars(nogc), sourceLength);
         } else {
             AutoCheckCannotGC nogc;
@@ -2570,26 +3135,28 @@ ImplicitConvert(JSContext* cx,
         }
 
         if (targetLength > sourceLength)
           dest[sourceLength] = 0;
 
         break;
       }
       default:
-        return TypeError(cx, "array", val);
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
 
     } else if (val.isObject() && JS_IsArrayObject(cx, valObj)) {
       // Convert each element of the array by calling ImplicitConvert.
       uint32_t sourceLength;
       if (!JS_GetArrayLength(cx, valObj, &sourceLength) ||
           targetLength != size_t(sourceLength)) {
-        JS_ReportError(cx, "ArrayType length does not match source array length");
-        return false;
+        MOZ_ASSERT(!funObj);
+        return ArrayLengthMismatch(cx, targetLength, targetType,
+                                   size_t(sourceLength), val, convType);
       }
 
       // Convert into an intermediate, in case of failure.
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       auto intermediate = cx->make_pod_array<char>(arraySize);
       if (!intermediate) {
         JS_ReportAllocationOverflow(cx);
@@ -2597,56 +3164,61 @@ ImplicitConvert(JSContext* cx,
       }
 
       for (uint32_t i = 0; i < sourceLength; ++i) {
         RootedValue item(cx);
         if (!JS_GetElement(cx, valObj, i, &item))
           return false;
 
         char* data = intermediate.get() + elementSize * i;
-        if (!ImplicitConvert(cx, item, baseType, data, false, nullptr))
+        if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr,
+                             funObj, argIndex, targetType, i))
           return false;
       }
 
       memcpy(buffer, intermediate.get(), arraySize);
 
     } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) {
       // Check that array is consistent with type, then
       // copy the array.
       uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
-        JS_ReportError(cx, "ArrayType length does not match source ArrayBuffer length");
-        return false;
+        MOZ_ASSERT(!funObj);
+        return ArrayLengthMismatch(cx, arraySize, targetType,
+                                   size_t(sourceLength), val, convType);
       }
       JS::AutoCheckCannotGC nogc;
       memcpy(buffer, JS_GetArrayBufferData(valObj, nogc), sourceLength);
       break;
     } else if (val.isObject() && JS_IsTypedArrayObject(valObj)) {
       // Check that array is consistent with type, then
       // copy the array.
       if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
-        return TypeError(cx, "typed array with the appropriate type", val);
+        return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                         arrObj, arrIndex);
       }
 
       uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
-        JS_ReportError(cx, "typed array length does not match source TypedArray length");
-        return false;
+        MOZ_ASSERT(!funObj);
+        return ArrayLengthMismatch(cx, arraySize, targetType,
+                                   size_t(sourceLength), val, convType);
       }
       JS::AutoCheckCannotGC nogc;
       memcpy(buffer, JS_GetArrayBufferViewData(valObj, nogc), sourceLength);
       break;
     } else {
       // Don't implicitly convert to string. Users can implicitly convert
       // with `String(x)` or `""+x`.
-      return TypeError(cx, "array", val);
+      return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                       arrObj, arrIndex);
     }
     break;
   }
   case TYPE_struct: {
     if (val.isObject() && !sourceData) {
       // Enumerate the properties of the object; if they match the struct
       // specification, convert the fields.
       AutoIdArray props(cx, JS_Enumerate(cx, valObj));
@@ -2658,66 +3230,70 @@ ImplicitConvert(JSContext* cx,
       auto intermediate = cx->make_pod_array<char>(structSize);
       if (!intermediate) {
         JS_ReportAllocationOverflow(cx);
         return false;
       }
 
       const FieldInfoHash* fields = StructType::GetFieldInfo(targetType);
       if (props.length() != fields->count()) {
-        JS_ReportError(cx, "missing fields");
-        return false;
+        return FieldCountMismatch(cx, fields->count(), targetType,
+                                  props.length(), val, convType,
+                                  funObj, argIndex);
       }
 
       RootedId id(cx);
       for (size_t i = 0; i < props.length(); ++i) {
         id = props[i];
 
         if (!JSID_IS_STRING(id)) {
-          JS_ReportError(cx, "property name is not a string");
-          return false;
+          return PropNameNonStringError(cx, id, val, convType,
+                                        funObj, argIndex);
         }
 
         JSFlatString* name = JSID_TO_FLAT_STRING(id);
         const FieldInfo* field = StructType::LookupField(cx, targetType, name);
         if (!field)
           return false;
 
         RootedValue prop(cx);
         if (!JS_GetPropertyById(cx, valObj, id, &prop))
           return false;
 
         // Convert the field via ImplicitConvert().
         char* fieldData = intermediate.get() + field->mOffset;
-        if (!ImplicitConvert(cx, prop, field->mType, fieldData, false, nullptr))
+        if (!ImplicitConvert(cx, prop, field->mType, fieldData, convType,
+                             nullptr, funObj, argIndex, targetType, i))
           return false;
       }
 
       memcpy(buffer, intermediate.get(), structSize);
       break;
     }
 
-    return TypeError(cx, "struct", val);
+    return ConvError(cx, targetType, val, convType, funObj, argIndex,
+                     arrObj, arrIndex);
   }
   case TYPE_void_t:
   case TYPE_function:
     MOZ_CRASH("invalid type");
   }
 
   return true;
 }
 
 // Convert jsval 'val' to a C binary representation of CType 'targetType',
 // storing the result in 'buffer'. This function is more forceful than
 // ImplicitConvert.
 static bool
-ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType, void* buffer)
+ExplicitConvert(JSContext* cx, HandleValue val, HandleObject targetType,
+                void* buffer, ConversionType convType)
 {
   // If ImplicitConvert succeeds, use that result.
-  if (ImplicitConvert(cx, val, targetType, buffer, false, nullptr))
+  if (ImplicitConvert(cx, val, targetType, buffer, convType, nullptr))
     return true;
 
   // If ImplicitConvert failed, and there is no pending exception, then assume
   // hard failure (out of memory, or some other similarly serious condition).
   // We store any pending exception in case we need to re-throw it.
   RootedValue ex(cx);
   if (!JS_GetPendingException(cx, &ex))
     return false;
@@ -2736,30 +3312,30 @@ ExplicitConvert(JSContext* cx, HandleVal
 #define INTEGRAL_CASE(name, type, ffiType)                                     \
   case TYPE_##name: {                                                          \
     /* Convert numeric values with a C-style cast, and */                      \
     /* allow conversion from a base-10 or base-16 string. */                   \
     type result;                                                               \
     if (!jsvalToIntegerExplicit(val, &result) &&                               \
         (!val.isString() ||                                                    \
          !StringToInteger(cx, val.toString(), &result)))                       \
-      return TypeError(cx, #name, val);                                        \
+      return ConvError(cx, #name, val, convType);                              \
     *static_cast<type*>(buffer) = result;                                      \
     break;                                                                     \
   }
   CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
   CTYPES_FOR_EACH_WRAPPED_INT_TYPE(INTEGRAL_CASE)
   CTYPES_FOR_EACH_CHAR_TYPE(INTEGRAL_CASE)
   CTYPES_FOR_EACH_CHAR16_TYPE(INTEGRAL_CASE)
 #undef INTEGRAL_CASE
   case TYPE_pointer: {
     // Convert a number, Int64 object, or UInt64 object to a pointer.
     uintptr_t result;
     if (!jsvalToPtrExplicit(cx, val, &result))
-      return TypeError(cx, "pointer", val);
+      return ConvError(cx, targetType, val, convType);
     *static_cast<uintptr_t*>(buffer) = result;
     break;
   }
   case TYPE_float32_t:
   case TYPE_float64_t:
   case TYPE_float:
   case TYPE_double:
   case TYPE_array:
@@ -3263,17 +3839,18 @@ CType::ConstructBasic(JSContext* cx,
   }
 
   // construct a CData object
   RootedObject result(cx, CData::Create(cx, obj, NullPtr(), nullptr, true));
   if (!result)
     return false;
 
   if (args.length() == 1) {
-    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result)))
+    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result),
+                         ConversionType::Construct))
       return false;
   }
 
   args.rval().setObject(*result);
   return true;
 }
 
 JSObject*
@@ -4064,17 +4641,18 @@ PointerType::ConstructData(JSContext* cx
   //
   // Case 2 - Initialized pointer
   //
   if (!looksLikeClosure) {
     if (args.length() != 1) {
       JS_ReportError(cx, "first argument must be a function");
       return false;
     }
-    return ExplicitConvert(cx, args[0], obj, CData::GetData(result));
+    return ExplicitConvert(cx, args[0], obj, CData::GetData(result),
+                           ConversionType::Construct);
   }
 
   //
   // Case 3 - Closure
   //
 
   // The second argument is an optional 'this' parameter with which to invoke
   // the given js function. Callers may leave this blank, or pass null if they
@@ -4248,17 +4826,18 @@ PointerType::ContentsSetter(JSContext* c
 
   void* data = *static_cast<void**>(CData::GetData(obj));
   if (data == nullptr) {
     JS_ReportError(cx, "cannot write contents to null pointer");
     return false;
   }
 
   args.rval().setUndefined();
-  return ImplicitConvert(cx, args.get(0), baseType, data, false, nullptr);
+  return ImplicitConvert(cx, args.get(0), baseType, data,
+                         ConversionType::Setter, nullptr);
 }
 
 /*******************************************************************************
 ** ArrayType implementation
 *******************************************************************************/
 
 bool
 ArrayType::Create(JSContext* cx, unsigned argc, jsval* vp)
@@ -4415,17 +4994,17 @@ ArrayType::ConstructData(JSContext* cx,
 
         ++length;
         break;
       }
       case TYPE_char16_t:
         length = sourceLength + 1;
         break;
       default:
-        return TypeError(cx, "array", args[0]);
+        return ConvError(cx, obj, args[0], ConversionType::Construct);
       }
 
     } else {
       JS_ReportError(cx, "argument must be an array object or length");
       return false;
     }
 
     // Construct a new ArrayType of defined length, for the new CData object.
@@ -4436,17 +5015,18 @@ ArrayType::ConstructData(JSContext* cx,
 
   JSObject* result = CData::Create(cx, obj, NullPtr(), nullptr, true);
   if (!result)
     return false;
 
   args.rval().setObject(*result);
 
   if (convertObject) {
-    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result)))
+    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result),
+                         ConversionType::Construct))
       return false;
   }
 
   return true;
 }
 
 JSObject*
 ArrayType::GetBaseType(JSObject* obj)
@@ -4606,17 +5186,18 @@ ArrayType::Getter(JSContext* cx, HandleO
   if (CType::GetTypeCode(typeObj) != TYPE_array)
     return true;
 
   // Convert the index to a size_t and bounds-check it.
   size_t index;
   size_t length = GetLength(typeObj);
   bool ok = jsidToSize(cx, idval, true, &index);
   int32_t dummy;
-  if (!ok && JSID_IS_STRING(idval) && !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
+  if (!ok && JSID_IS_STRING(idval) &&
+      !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
     // String either isn't a number, or doesn't fit in size_t.
     // Chances are it's a regular property lookup, so return.
     return true;
   }
   if (!ok || index >= length) {
     JS_ReportError(cx, "invalid index");
     return false;
   }
@@ -4634,39 +5215,41 @@ ArrayType::Setter(JSContext* cx, HandleO
   // This should never happen, but we'll check to be safe.
   if (!CData::IsCData(obj)) {
     JS_ReportError(cx, "not a CData");
     return false;
   }
 
   // Bail early if we're not an ArrayType. (This setter is present for all
   // CData, regardless of CType.)
-  JSObject* typeObj = CData::GetCType(obj);
+  RootedObject typeObj(cx, CData::GetCType(obj));
   if (CType::GetTypeCode(typeObj) != TYPE_array)
     return result.succeed();
 
   // Convert the index to a size_t and bounds-check it.
   size_t index;
   size_t length = GetLength(typeObj);
   bool ok = jsidToSize(cx, idval, true, &index);
   int32_t dummy;
-  if (!ok && JSID_IS_STRING(idval) && !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
+  if (!ok && JSID_IS_STRING(idval) &&
+      !StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
     // String either isn't a number, or doesn't fit in size_t.
     // Chances are it's a regular property lookup, so return.
     return result.succeed();
   }
   if (!ok || index >= length) {
     JS_ReportError(cx, "invalid index");
     return false;
   }
 
-  JSObject* baseType = GetBaseType(typeObj);
+  RootedObject baseType(cx, GetBaseType(typeObj));
   size_t elementSize = CType::GetSize(baseType);
   char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index;
-  if (!ImplicitConvert(cx, vp, baseType, data, false, nullptr))
+  if (!ImplicitConvert(cx, vp, baseType, data, ConversionType::Setter,
+                       nullptr, NullPtr(), 0, typeObj, index))
     return false;
   return result.succeed();
 }
 
 bool
 ArrayType::AddressOfElement(JSContext* cx, unsigned argc, jsval* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
@@ -5151,17 +5734,17 @@ StructType::ConstructData(JSContext* cx,
     // 1) It may be an object '{ ... }' with properties representing the
     //    struct fields intended to ExplicitConvert wholesale to our StructType.
     // 2) If the struct contains one field, the arg may be intended to
     //    ImplicitConvert directly to that arg's CType.
     // Thankfully, the conditions for these two possibilities to succeed
     // are mutually exclusive, so we can pick the right one.
 
     // Try option 1) first.
-    if (ExplicitConvert(cx, args[0], obj, buffer))
+    if (ExplicitConvert(cx, args[0], obj, buffer, ConversionType::Construct))
       return true;
 
     if (fields->count() != 1)
       return false;
 
     // If ExplicitConvert failed, and there is no pending exception, then assume
     // hard failure (out of memory, or some other similarly serious condition).
     if (!JS_IsExceptionPending(cx))
@@ -5176,18 +5759,18 @@ StructType::ConstructData(JSContext* cx,
 
   // We have a type constructor of the form 'ctypes.StructType(a, b, c, ...)'.
   // ImplicitConvert each field.
   if (args.length() == fields->count()) {
     for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
       const FieldInfo& field = r.front().value();
       STATIC_ASSUME(field.mIndex < fields->count());  /* Quantified invariant */
       if (!ImplicitConvert(cx, args[field.mIndex], field.mType,
-             buffer + field.mOffset,
-             false, nullptr))
+                           buffer + field.mOffset, ConversionType::Construct,
+                           nullptr, NullPtr(), 0, obj, field.mIndex))
         return false;
     }
 
     return true;
   }
 
   JS_ReportError(cx, "constructor takes 0, 1, or %u arguments",
     fields->count());
@@ -5341,17 +5924,17 @@ StructType::FieldSetter(JSContext* cx, u
   }
 
   RootedObject obj(cx, &args.thisv().toObject());
   if (!CData::IsCData(obj)) {
     JS_ReportError(cx, "not a CData");
     return false;
   }
 
-  JSObject* typeObj = CData::GetCType(obj);
+  RootedObject typeObj(cx, CData::GetCType(obj));
   if (CType::GetTypeCode(typeObj) != TYPE_struct) {
     JS_ReportError(cx, "not a StructType");
     return false;
   }
 
   RootedValue nameVal(cx, GetFunctionNativeReserved(&args.callee(), SLOT_FIELDNAME));
   Rooted<JSFlatString*> name(cx, JS_FlattenString(cx, nameVal.toString()));
   if (!name)
@@ -5359,17 +5942,18 @@ StructType::FieldSetter(JSContext* cx, u
 
   const FieldInfo* field = LookupField(cx, typeObj, name);
   if (!field)
     return false;
 
   args.rval().setUndefined();
 
   char* data = static_cast<char*>(CData::GetData(obj)) + field->mOffset;
-  return ImplicitConvert(cx, args.get(0), field->mType, data, false, nullptr);
+  return ImplicitConvert(cx, args.get(0), field->mType, data, ConversionType::Setter, nullptr,
+                         NullPtr(), 0, typeObj, field->mIndex);
 }
 
 bool
 StructType::AddressOfField(JSContext* cx, unsigned argc, jsval* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
@@ -5845,28 +6429,32 @@ FunctionType::ConstructData(JSContext* c
   // could be called on a frozen object.
   return JS_FreezeObject(cx, dataObj);
 }
 
 typedef Array<AutoValue, 16> AutoValueAutoArray;
 
 static bool
 ConvertArgument(JSContext* cx,
+                HandleObject funObj,
+                unsigned argIndex,
                 HandleValue arg,
                 JSObject* type,
                 AutoValue* value,
                 AutoValueAutoArray* strings)
 {
   if (!value->SizeToType(cx, type)) {
     JS_ReportAllocationOverflow(cx);
     return false;
   }
 
   bool freePointer = false;
-  if (!ImplicitConvert(cx, arg, type, value->mData, true, &freePointer))
+  if (!ImplicitConvert(cx, arg, type, value->mData,
+                       ConversionType::Argument, &freePointer,
+                       funObj, argIndex))
     return false;
 
   if (freePointer) {
     // ImplicitConvert converted a string for us, which we have to free.
     // Keep track of it.
     if (!strings->growBy(1)) {
       JS_ReportOutOfMemory(cx);
       return false;
@@ -5925,17 +6513,18 @@ FunctionType::Call(JSContext* cx,
   AutoValueAutoArray values;
   AutoValueAutoArray strings;
   if (!values.resize(args.length())) {
     JS_ReportOutOfMemory(cx);
     return false;
   }
 
   for (unsigned i = 0; i < argcFixed; ++i)
-    if (!ConvertArgument(cx, args[i], fninfo->mArgTypes[i], &values[i], &strings))
+    if (!ConvertArgument(cx, obj, i, args[i], fninfo->mArgTypes[i],
+                         &values[i], &strings))
       return false;
 
   if (fninfo->mIsVariadic) {
     if (!fninfo->mFFITypes.resize(args.length())) {
       JS_ReportOutOfMemory(cx);
       return false;
     }
 
@@ -5950,17 +6539,17 @@ FunctionType::Call(JSContext* cx,
         JS_ReportError(cx, "argument %d of type %s is not a CData object",
                        i, InformalValueTypeName(args[i]));
         return false;
       }
       if (!(type = CData::GetCType(obj)) ||
           !(type = PrepareType(cx, OBJECT_TO_JSVAL(type))) ||
           // Relying on ImplicitConvert only for the limited purpose of
           // converting one CType to another (e.g., T[] to T*).
-          !ConvertArgument(cx, args[i], type, &values[i], &strings) ||
+          !ConvertArgument(cx, obj, i, args[i], type, &values[i], &strings) ||
           !(fninfo->mFFITypes[i] = CType::GetFFIType(cx, type))) {
         // These functions report their own errors.
         return false;
       }
     }
     if (!PrepareCIF(cx, fninfo))
       return false;
   }
@@ -6176,17 +6765,18 @@ CClosure::Create(JSContext* cx,
 
     // Allocate a buffer for the return value.
     size_t rvSize = CType::GetSize(fninfo->mReturnType);
     errResult = result->zone()->make_pod_array<uint8_t>(rvSize);
     if (!errResult)
       return nullptr;
 
     // Do the value conversion. This might fail, in which case we throw.
-    if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(), false, nullptr))
+    if (!ImplicitConvert(cx, errVal, fninfo->mReturnType, errResult.get(),
+                         ConversionType::Return, nullptr, typeObj))
       return nullptr;
   }
 
   ClosureInfo* cinfo = cx->new_<ClosureInfo>(JS_GetRuntime(cx));
   if (!cinfo) {
     JS_ReportOutOfMemory(cx);
     return nullptr;
   }
@@ -6323,24 +6913,24 @@ CClosure::ClosureStub(ffi_cif* cif, void
       return;
   }
 
   // Call the JS function. 'thisObj' may be nullptr, in which case the JS
   // engine will find an appropriate object to use.
   RootedValue rval(cx);
   bool success = JS_CallFunctionValue(cx, thisObj, jsfnVal, argv, &rval);
 
-  // Convert the result. Note that we pass 'isArgument = false', such that
+  // Convert the result. Note that we pass 'ConversionType::Return', such that
   // ImplicitConvert will *not* autoconvert a JS string into a pointer-to-char
   // type, which would require an allocation that we can't track. The JS
   // function must perform this conversion itself and return a PointerType
   // CData; thusly, the burden of freeing the data is left to the user.
   if (success && cif->rtype != &ffi_type_void)
-    success = ImplicitConvert(cx, rval, fninfo->mReturnType, result, false,
-                              nullptr);
+    success = ImplicitConvert(cx, rval, fninfo->mReturnType, result,
+                              ConversionType::Return, nullptr, typeObj);
 
   if (!success) {
     // Something failed. The callee may have thrown, or it may not have
     // returned a value that ImplicitConvert() was happy with. Depending on how
     // prudent the consumer has been, we may or may not have a recovery plan.
 
     // In any case, a JS exception cannot be passed to C code, so report the
     // exception if any and clear it from the cx.
@@ -6557,17 +7147,18 @@ CData::ValueGetter(JSContext* cx, JS::Ca
   return ConvertToJS(cx, ctype, NullPtr(), GetData(obj), true, false, args.rval());
 }
 
 bool
 CData::ValueSetter(JSContext* cx, JS::CallArgs args)
 {
   RootedObject obj(cx, &args.thisv().toObject());
   args.rval().setUndefined();
-  return ImplicitConvert(cx, args.get(0), GetCType(obj), GetData(obj), false, nullptr);
+  return ImplicitConvert(cx, args.get(0), GetCType(obj), GetData(obj),
+                         ConversionType::Setter, nullptr);
 }
 
 bool
 CData::Address(JSContext* cx, unsigned argc, jsval* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   if (args.length() != 0) {
     JS_ReportError(cx, "address takes zero arguments");
@@ -7045,31 +7636,31 @@ CDataFinalizer::Construct(JSContext* cx,
   }
   RootedObject objCodePtrType(cx, CData::GetCType(objCodePtr));
   RootedValue valCodePtrType(cx, ObjectValue(*objCodePtrType));
   MOZ_ASSERT(objCodePtrType);
 
   TypeCode typCodePtr = CType::GetTypeCode(objCodePtrType);
   if (typCodePtr != TYPE_pointer) {
     return TypeError(cx, "a CData object of a function _pointer_ type",
-                     valCodePtrType);
+                     valCodePtr);
   }
 
   JSObject* objCodeType = PointerType::GetBaseType(objCodePtrType);
   MOZ_ASSERT(objCodeType);
 
   TypeCode typCode = CType::GetTypeCode(objCodeType);
   if (typCode != TYPE_function) {
     return TypeError(cx, "a CData object of a _function_ pointer type",
-                     valCodePtrType);
+                     valCodePtr);
   }
   uintptr_t code = *reinterpret_cast<uintptr_t*>(CData::GetData(objCodePtr));
   if (!code) {
     return TypeError(cx, "a CData object of a _non-NULL_ function pointer type",
-                     valCodePtrType);
+                     valCodePtr);
   }
 
   FunctionInfo* funInfoFinalizer =
     FunctionType::GetFunctionInfo(objCodeType);
   MOZ_ASSERT(funInfoFinalizer);
 
   if ((funInfoFinalizer->mArgTypes.length() != 1)
       || (funInfoFinalizer->mIsVariadic)) {
@@ -7085,26 +7676,27 @@ CDataFinalizer::Construct(JSContext* cx,
 
   bool freePointer = false;
 
   // 3. Perform dynamic cast of |args[0]| into |objType|, store it in |cargs|
 
   size_t sizeArg;
   RootedValue valData(cx, args[0]);
   if (!CType::GetSafeSize(objArgType, &sizeArg)) {
-    return TypeError(cx, "(an object with known size)", valData);
+    RootedValue valCodeType(cx, ObjectValue(*objCodeType));
+    return TypeError(cx, "a function with one known size argument",
+                     valCodeType);
   }
 
   ScopedJSFreePtr<void> cargs(malloc(sizeArg));
 
   if (!ImplicitConvert(cx, valData, objArgType, cargs.get(),
-                       false, &freePointer)) {
-    RootedValue valArgType(cx, ObjectValue(*objArgType));
-    return TypeError(cx, "(an object that can be converted to the following type)",
-                     valArgType);
+                       ConversionType::Finalizer, &freePointer,
+                       objCodePtrType, 0)) {
+    return false;
   }
   if (freePointer) {
     // Note: We could handle that case, if necessary.
     JS_ReportError(cx, "Internal Error during CDataFinalizer. Object cannot be represented");
     return false;
   }
 
   // 4. Prepare buffer for holding return value
@@ -7130,17 +7722,17 @@ CDataFinalizer::Construct(JSContext* cx,
     JSObject* objData = &valData.toObject();
     if (CData::IsCData(objData)) {
       objBestArgType = CData::GetCType(objData);
       size_t sizeBestArg;
       if (!CType::GetSafeSize(objBestArgType, &sizeBestArg)) {
         MOZ_CRASH("object with unknown size");
       }
       if (sizeBestArg != sizeArg) {
-        return TypeError(cx, "(an object with the same size as that expected by the C finalization function)", valData);
+        return FinalizerSizeError(cx, objCodePtrType, valData);
       }
     }
   }
 
   // Used by GetCType
   JS_SetReservedSlot(objResult,
                      SLOT_DATAFINALIZER_VALTYPE,
                      OBJECT_TO_JSVAL(objBestArgType));
@@ -7241,18 +7833,18 @@ CDataFinalizer::Methods::Forget(JSContex
     JS_ReportError(cx, "CDataFinalizer.prototype.forget takes no arguments");
     return false;
   }
 
   JS::Rooted<JSObject*> obj(cx, args.thisv().toObjectOrNull());
   if (!obj)
     return false;
   if (!CDataFinalizer::IsCDataFinalizer(obj)) {
-    RootedValue val(cx, ObjectValue(*obj));
-    return TypeError(cx, "a CDataFinalizer", val);
+    JS_ReportError(cx, "not a CDataFinalizer");
+    return false;
   }
 
   CDataFinalizer::Private* p = (CDataFinalizer::Private*)
     JS_GetPrivate(obj);
 
   if (!p) {
     JS_ReportError(cx, "forget called on an empty CDataFinalizer");
     return false;
@@ -7289,18 +7881,18 @@ CDataFinalizer::Methods::Dispose(JSConte
     JS_ReportError(cx, "CDataFinalizer.prototype.dispose takes no arguments");
     return false;
   }
 
   RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
   if (!CDataFinalizer::IsCDataFinalizer(obj)) {
-    RootedValue val(cx, ObjectValue(*obj));
-    return TypeError(cx, "a CDataFinalizer", val);
+    JS_ReportError(cx, "not a CDataFinalizer");
+    return false;
   }
 
   CDataFinalizer::Private* p = (CDataFinalizer::Private*)
     JS_GetPrivate(obj);
 
   if (!p) {
     JS_ReportError(cx, "dispose called on an empty CDataFinalizer.");
     return false;
@@ -7529,18 +8121,19 @@ Int64::Construct(JSContext* cx,
 
   // Construct and return a new Int64 object.
   if (args.length() != 1) {
     JS_ReportError(cx, "Int64 takes one argument");
     return false;
   }
 
   int64_t i = 0;
-  if (!jsvalToBigInteger(cx, args[0], true, &i))
-    return TypeError(cx, "int64", args[0]);
+  if (!jsvalToBigInteger(cx, args[0], true, &i)) {
+    return ArgumentConvError(cx, args[0], "Int64", 0);
+  }
 
   // Get ctypes.Int64.prototype from the 'prototype' property of the ctor.
   RootedValue slot(cx);
   RootedObject callee(cx, &args.callee());
   ASSERT_OK(JS_GetProperty(cx, callee, "prototype", &slot));
   RootedObject proto(cx, slot.toObjectOrNull());
   MOZ_ASSERT(JS_GetClass(proto) == &sInt64ProtoClass);
 
@@ -7664,19 +8257,19 @@ Int64::Join(JSContext* cx, unsigned argc
   if (args.length() != 2) {
     JS_ReportError(cx, "join takes two arguments");
     return false;
   }
 
   int32_t hi;
   uint32_t lo;
   if (!jsvalToInteger(cx, args[0], &hi))
-    return TypeError(cx, "int32", args[0]);
+    return ArgumentConvError(cx, args[0], "Int64.join", 0);
   if (!jsvalToInteger(cx, args[1], &lo))
-    return TypeError(cx, "uint32", args[1]);
+    return ArgumentConvError(cx, args[1], "Int64.join", 1);
 
   int64_t i = (int64_t(hi) << 32) + int64_t(lo);
 
   // Get Int64.prototype from the function's reserved slot.
   JSObject* callee = &args.callee();
 
   jsval slot = js::GetFunctionNativeReserved(callee, SLOT_FN_INT64PROTO);
   RootedObject proto(cx, &slot.toObject());
@@ -7699,18 +8292,19 @@ UInt64::Construct(JSContext* cx,
 
   // Construct and return a new UInt64 object.
   if (args.length() != 1) {
     JS_ReportError(cx, "UInt64 takes one argument");
     return false;
   }
 
   uint64_t u = 0;
-  if (!jsvalToBigInteger(cx, args[0], true, &u))
-    return TypeError(cx, "uint64", args[0]);
+  if (!jsvalToBigInteger(cx, args[0], true, &u)) {
+    return ArgumentConvError(cx, args[0], "UInt64", 0);
+  }
 
   // Get ctypes.UInt64.prototype from the 'prototype' property of the ctor.
   RootedValue slot(cx);
   RootedObject callee(cx, &args.callee());
   ASSERT_OK(JS_GetProperty(cx, callee, "prototype", &slot));
   RootedObject proto(cx, &slot.toObject());
   MOZ_ASSERT(JS_GetClass(proto) == &sUInt64ProtoClass);
 
@@ -7830,19 +8424,19 @@ UInt64::Join(JSContext* cx, unsigned arg
   if (args.length() != 2) {
     JS_ReportError(cx, "join takes two arguments");
     return false;
   }
 
   uint32_t hi;
   uint32_t lo;
   if (!jsvalToInteger(cx, args[0], &hi))
-    return TypeError(cx, "uint32_t", args[0]);
+    return ArgumentConvError(cx, args[0], "UInt64.join", 0);
   if (!jsvalToInteger(cx, args[1], &lo))
-    return TypeError(cx, "uint32_t", args[1]);
+    return ArgumentConvError(cx, args[1], "UInt64.join", 1);
 
   uint64_t u = (uint64_t(hi) << 32) + uint64_t(lo);
 
   // Get UInt64.prototype from the function's reserved slot.
   JSObject* callee = &args.callee();
 
   jsval slot = js::GetFunctionNativeReserved(callee, SLOT_FN_INT64PROTO);
   RootedObject proto(cx, &slot.toObject());
--- a/js/src/ctypes/CTypes.h
+++ b/js/src/ctypes/CTypes.h
@@ -5,16 +5,17 @@
 
 #ifndef ctypes_CTypes_h
 #define ctypes_CTypes_h
 
 #include "mozilla/UniquePtr.h"
 
 #include "ffi.h"
 #include "jsalloc.h"
+#include "jsprf.h"
 #include "prlink.h"
 
 #include "ctypes/typedefs.h"
 #include "js/HashTable.h"
 #include "js/Vector.h"
 #include "vm/String.h"
 
 namespace js {
@@ -48,16 +49,42 @@ AppendString(Vector<T, N, AP>& v, const 
   size_t vlen = v.length();
   if (!v.resize(vlen + alen))
     return;
 
   for (size_t i = 0; i < alen; ++i)
     v[i + vlen] = array[i];
 }
 
+template <class T, size_t N, class AP>
+void
+AppendChars(Vector<T, N, AP>& v, const char c, size_t count)
+{
+  size_t vlen = v.length();
+  if (!v.resize(vlen + count))
+    return;
+
+  for (size_t i = 0; i < count; ++i)
+    v[i + vlen] = c;
+}
+
+template <class T, size_t N, class AP>
+void
+AppendUInt(Vector<T, N, AP>& v, unsigned n)
+{
+  char array[16];
+  size_t alen = JS_snprintf(array, 16, "%u", n);
+  size_t vlen = v.length();
+  if (!v.resize(vlen + alen))
+    return;
+
+  for (size_t i = 0; i < alen; ++i)
+    v[i + vlen] = array[i];
+}
+
 template <class T, size_t N, size_t M, class AP>
 void
 AppendString(Vector<T, N, AP>& v, Vector<T, M, AP>& w)
 {
   v.append(w.begin(), w.length());
 }
 
 template <size_t N, class AP>
@@ -370,16 +397,17 @@ enum CTypeSlot {
   CTYPE_SLOTS
 };
 
 enum CDataSlot {
   SLOT_CTYPE    = 0, // CType object representing the underlying type
   SLOT_REFERENT = 1, // JSObject this object must keep alive, if any
   SLOT_DATA     = 2, // pointer to a buffer containing the binary data
   SLOT_OWNS     = 3, // JSVAL_TRUE if this CData owns its own buffer
+  SLOT_FUNNAME  = 4, // JSString representing the function name
   CDATA_SLOTS
 };
 
 enum CClosureSlot {
   SLOT_CLOSUREINFO = 0, // ClosureInfo struct
   CCLOSURE_SLOTS
 };
 
--- a/js/src/ctypes/Library.cpp
+++ b/js/src/ctypes/Library.cpp
@@ -316,17 +316,17 @@ Library::Declare(JSContext* cx, unsigned
     if (CType::GetTypeCode(typeObj) == TYPE_pointer) {
       fnObj = PointerType::GetBaseType(typeObj);
       isFunction = fnObj && CType::GetTypeCode(fnObj) == TYPE_function;
     }
   }
 
   void* data;
   PRFuncPtr fnptr;
-  JSString* nameStr = args[0].toString();
+  RootedString nameStr(cx, args[0].toString());
   AutoCString symbol;
   if (isFunction) {
     // Build the symbol, with mangling if necessary.
     FunctionType::BuildSymbolName(nameStr, fnObj, symbol);
     AppendString(symbol, "\0");
 
     // Look up the function symbol.
     fnptr = PR_FindFunctionSymbol(library, symbol.begin());
@@ -347,16 +347,19 @@ Library::Declare(JSContext* cx, unsigned
       return false;
     }
   }
 
   RootedObject result(cx, CData::Create(cx, typeObj, obj, data, isFunction));
   if (!result)
     return false;
 
+  if (isFunction)
+    JS_SetReservedSlot(result, SLOT_FUNNAME, StringValue(nameStr));
+
   args.rval().setObject(*result);
 
   // Seal the CData object, to prevent modification of the function pointer.
   // This permanently associates this object with the library, and avoids
   // having to do things like reset SLOT_REFERENT when someone tries to
   // change the pointer value.
   // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
   // could be called on a sealed object.
--- a/js/src/ctypes/ctypes.msg
+++ b/js/src/ctypes/ctypes.msg
@@ -5,10 +5,30 @@
 
 /*
  * This is the jsctypes error message file.
  *
  * For syntax details, see js/src/js.msg.
  */
 
 MSG_DEF(CTYPESMSG_PLACEHOLDER_0, 0, JSEXN_NONE, NULL)
-MSG_DEF(CTYPESMSG_TYPE_ERROR,    2, JSEXN_TYPEERR, "expected type {0}, got {1}")
+
+/* type conversion */
+MSG_DEF(CTYPESMSG_CONV_ERROR_ARG,3, JSEXN_TYPEERR, "can't pass {0} to argument {1} of {2}")
+MSG_DEF(CTYPESMSG_CONV_ERROR_ARRAY,3, JSEXN_TYPEERR, "can't convert {0} to element {1} of the type {2}")
+MSG_DEF(CTYPESMSG_CONV_ERROR_FIN,2, JSEXN_TYPEERR, "can't convert {0} to the type of argument 1 of {1}")
+MSG_DEF(CTYPESMSG_CONV_ERROR_RET,2, JSEXN_TYPEERR, "can't convert {0} to the return type of {1}")
+MSG_DEF(CTYPESMSG_CONV_ERROR_SET,2, JSEXN_TYPEERR, "can't convert {0} to the type {1}")
+MSG_DEF(CTYPESMSG_CONV_ERROR_STRUCT,5, JSEXN_TYPEERR, "can't convert {0} to the '{1}' field ({2}) of {3}{4}")
+MSG_DEF(CTYPESMSG_NON_PRIMITIVE, 1, JSEXN_TYPEERR, ".value only works on character and numeric types, not `{0}`")
+MSG_DEF(CTYPESMSG_TYPE_ERROR,    2, JSEXN_TYPEERR, "expected {0}, got {1}")
 
+/* array */
+MSG_DEF(CTYPESMSG_ARRAY_MISMATCH,4, JSEXN_TYPEERR, "length of {0} does not match to the length of the type {1} (expected {2}, got {3})")
+MSG_DEF(CTYPESMSG_ARRAY_OVERFLOW,4, JSEXN_TYPEERR, "length of {0} does not fit to the length of the type {1} (expected {2} or lower, got {3})")
+
+/* struct */
+MSG_DEF(CTYPESMSG_FIELD_MISMATCH,5, JSEXN_TYPEERR, "property count of {0} does not match to field count of the type {1} (expected {2}, got {3}){4}")
+MSG_DEF(CTYPESMSG_PROP_NONSTRING,3, JSEXN_TYPEERR, "property name {0} of {1} is not a string{2}")
+
+/* data finalizer */
+MSG_DEF(CTYPESMSG_EMPTY_FIN,     1, JSEXN_TYPEERR, "attempting to convert an empty CDataFinalizer{0}")
+MSG_DEF(CTYPESMSG_FIN_SIZE_ERROR,2, JSEXN_TYPEERR, "expected an object with the same size as argument 1 of {0}, got {1}")
--- a/js/src/jit-test/lib/asserts.js
+++ b/js/src/jit-test/lib/asserts.js
@@ -60,8 +60,28 @@ if (typeof assertNoWarning === 'undefine
                   "with warnings-as-errors enabled");
             throw exc;
         } finally {
             if (!hadWerror)
                 options("werror");
         }
     };
 }
+
+if (typeof assertTypeErrorMessage === 'undefined') {
+    var assertTypeErrorMessage = function assertTypeErrorMessage(f, test) {
+        try {
+            f();
+        } catch (e) {
+            if (!(e instanceof TypeError))
+                throw new Error("Assertion failed: expected exception TypeError, got " + e);
+            if (typeof test == "string") {
+                if (test != e.message)
+                    throw new Error("Assertion failed: expeceted " + test + ", got " + e.message);
+            } else {
+                if (!test.test(e.message))
+                    throw new Error("Assertion failed: expeceted " + test.toString() + ", got " + e.message);
+            }
+            return;
+        }
+        throw new Error("Assertion failed: expected exception TypeError, no exception thrown");
+    };
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-array.js
@@ -0,0 +1,36 @@
+// Type conversion error should report its type.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  // constructor
+  assertTypeErrorMessage(() => { ctypes.int32_t.array()("foo"); },
+                         "can't convert the string \"foo\" to the type ctypes.int32_t.array()");
+  assertTypeErrorMessage(() => { ctypes.int32_t.array(10)("foo"); },
+                         "can't convert the string \"foo\" to the type ctypes.int32_t.array(10)");
+  assertTypeErrorMessage(() => { ctypes.char.array(2)("foo"); },
+                         "length of the string \"foo\" does not fit to the length of the type ctypes.char.array(2) (expected 2 or lower, got 3)");
+  assertTypeErrorMessage(() => { ctypes.char16_t.array(2)("foo"); },
+                         "length of the string \"foo\" does not fit to the length of the type ctypes.char16_t.array(2) (expected 2 or lower, got 3)");
+  assertTypeErrorMessage(() => { ctypes.int8_t.array(2)(new ArrayBuffer(8)); },
+                         "length of the array buffer ({}) does not match to the length of the type ctypes.int8_t.array(2) (expected 2, got 8)");
+  assertTypeErrorMessage(() => { ctypes.int8_t.array(2)(new Int8Array(8)); },
+                         "length of the typed array ({0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0}) does not match to the length of the type ctypes.int8_t.array(2) (expected 2, got 8)");
+
+  // elem setter
+  assertTypeErrorMessage(() => { ctypes.int32_t.array(10)()[0] = "foo"; },
+                         "can't convert the string \"foo\" to element 0 of the type ctypes.int32_t.array(10)");
+  assertTypeErrorMessage(() => { ctypes.int32_t.array(10)()[1] = "foo"; },
+                         "can't convert the string \"foo\" to element 1 of the type ctypes.int32_t.array(10)");
+
+  // value setter
+  assertTypeErrorMessage(() => { ctypes.int32_t.array(1)().value = ["foo"]; },
+                         "can't convert the string \"foo\" to element 0 of the type ctypes.int32_t.array(1)");
+  assertTypeErrorMessage(() => { ctypes.int32_t.array(1)().value = [2, "foo"]; },
+                         "length of the array [2, \"foo\"] does not match to the length of the type ctypes.int32_t.array(1) (expected 1, got 2)");
+  assertTypeErrorMessage(() => { ctypes.int32_t.array(2)().value = [2, "foo"]; },
+                         "can't convert the string \"foo\" to element 1 of the type ctypes.int32_t.array(2)");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-error.js
@@ -0,0 +1,14 @@
+load(libdir + 'asserts.js');
+
+function test() {
+  let obj = {
+    toSource() {
+      throw 1;
+    }
+  };
+  assertTypeErrorMessage(() => { ctypes.double().value = obj; },
+                         "can't convert <<error converting value to string>> to the type double");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-finalizer.js
@@ -0,0 +1,61 @@
+load(libdir + 'asserts.js');
+
+function test() {
+  // non object
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, "foo"); },
+                         "expected _a CData object_ of a function pointer type, got the string \"foo\"");
+  // non CData object
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, ["foo"]); },
+                         "expected a _CData_ object of a function pointer type, got the array [\"foo\"]");
+
+  // a CData which is not a pointer
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, ctypes.int32_t(0)); },
+                         "expected a CData object of a function _pointer_ type, got ctypes.int32_t(0)");
+  // a pointer CData which is not a function
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, ctypes.int32_t.ptr(0)); },
+                         "expected a CData object of a _function_ pointer type, got ctypes.int32_t.ptr(ctypes.UInt64(\"0x0\"))");
+
+  // null function
+  let func_type = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t,
+                                      [ctypes.int32_t, ctypes.int32_t]).ptr;
+  let f0 = func_type(0);
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, f0); },
+                         "expected a CData object of a _non-NULL_ function pointer type, got ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t, ctypes.int32_t]).ptr(ctypes.UInt64(\"0x0\"))");
+
+  // a function with 2 arguments
+  let f1 = func_type(x => x);
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, f1); },
+                         "expected a function accepting exactly one argument, got ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t, ctypes.int32_t])");
+
+  // non CData in argument 1
+  let func_type2 = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t,
+                                       [ctypes.int32_t.ptr]).ptr;
+  let f2 = func_type2(x => x);
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(0, f2); },
+                         "can't convert the number 0 to the type of argument 1 of ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t.ptr]).ptr");
+
+  // wrong struct in argument 1
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]);
+  let func_type3 = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t,
+                                       [test_struct]).ptr;
+  let f3 = func_type3(x => x);
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer({ "x": "foo" }, f3); },
+                         "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct at argument 1 of ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [test_struct]).ptr");
+
+  // different size in argument 1
+  let func_type4 = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t,
+                                       [ctypes.int32_t]).ptr;
+  let f4 = func_type4(x => x);
+  assertTypeErrorMessage(() => { ctypes.CDataFinalizer(ctypes.int16_t(0), f4); },
+                         "expected an object with the same size as argument 1 of ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [ctypes.int32_t]).ptr, got ctypes.int16_t(0)");
+
+  let fin = ctypes.CDataFinalizer(ctypes.int32_t(0), f4);
+  fin.dispose();
+  assertTypeErrorMessage(() => { ctypes.int32_t(0).value = fin; },
+                         "attempting to convert an empty CDataFinalizer");
+  assertTypeErrorMessage(() => { f4(fin); },
+                         /attempting to convert an empty CDataFinalizer at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.int32_t, \[ctypes\.int32_t\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/);
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-function.js
@@ -0,0 +1,33 @@
+// Type conversion error should report its type.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  // Note: js shell cannot handle the exception in return value.
+
+  // primitive
+  let func_type = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t,
+                                      [ctypes.int32_t]).ptr;
+  let f1 = func_type(function() {});
+  assertTypeErrorMessage(() => { f1("foo"); },
+                         /can't pass the string "foo" to argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.voidptr_t, \[ctypes\.int32_t\]\)\.ptr\(ctypes\.UInt64\("[x0-9A-Fa-f]+"\)\)/);
+
+  // struct
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]);
+  let func_type2 = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t,
+                                       [test_struct]).ptr;
+  let f2 = func_type2(function() {});
+  assertTypeErrorMessage(() => { f2({ "x": "foo" }); },
+                         /can't convert the string \"foo\" to the 'x' field \(int32_t\) of test_struct at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes.int32_t, \[test_struct\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/);
+  assertTypeErrorMessage(() => { f2({ "x": "foo", "y": "bar" }); },
+                         /property count of the object \(\{x:\"foo\", y:\"bar\"\}\) does not match to field count of the type test_struct \(expected 1, got 2\) at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.int32_t, \[test_struct\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/);
+  assertTypeErrorMessage(() => { f2({ 0: "foo" }); },
+                         /property name the number 0 of the object \(\{0:\"foo\"\}\) is not a string at argument 1 of ctypes\.FunctionType\(ctypes\.default_abi, ctypes\.int32_t, \[test_struct\]\)\.ptr\(ctypes\.UInt64\(\"[x0-9A-Fa-f]+\"\)\)/);
+
+  // error sentinel
+  assertTypeErrorMessage(() => { func_type(function() {}, null, "foo"); },
+                         "can't convert the string \"foo\" to the return type of ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t, [ctypes.int32_t])");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-int64.js
@@ -0,0 +1,20 @@
+load(libdir + 'asserts.js');
+
+function test() {
+  assertTypeErrorMessage(() => { ctypes.Int64("0xfffffffffffffffffffffff"); },
+                         "can't pass the string \"0xfffffffffffffffffffffff\" to argument 1 of Int64");
+  assertTypeErrorMessage(() => { ctypes.Int64.join("foo", 0); },
+                         "can't pass the string \"foo\" to argument 1 of Int64.join");
+  assertTypeErrorMessage(() => { ctypes.Int64.join(0, "foo"); },
+                         "can't pass the string \"foo\" to argument 2 of Int64.join");
+
+  assertTypeErrorMessage(() => { ctypes.UInt64("0xfffffffffffffffffffffff"); },
+                         "can't pass the string \"0xfffffffffffffffffffffff\" to argument 1 of UInt64");
+  assertTypeErrorMessage(() => { ctypes.UInt64.join("foo", 0); },
+                         "can't pass the string \"foo\" to argument 1 of UInt64.join");
+  assertTypeErrorMessage(() => { ctypes.UInt64.join(0, "foo"); },
+                         "can't pass the string \"foo\" to argument 2 of UInt64.join");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-native-function.js
@@ -0,0 +1,37 @@
+// Type conversion error for native function should report its name and type
+// in C style.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  let lib;
+  try {
+    lib = ctypes.open(ctypes.libraryName("c"));
+  } catch (e) {
+  }
+  if (!lib)
+    return;
+
+  let func = lib.declare("hypot",
+                         ctypes.default_abi,
+                         ctypes.double,
+                         ctypes.double, ctypes.double);
+  assertTypeErrorMessage(() => { func(1, "xyzzy"); },
+                         "can't pass the string \"xyzzy\" to argument 2 of double hypot(double, double)");
+
+  // test C style source for various types
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]);
+  let test_func = ctypes.FunctionType(ctypes.default_abi, ctypes.voidptr_t,
+                                      [ctypes.int32_t]).ptr;
+  func = lib.declare("hypot",
+                     ctypes.default_abi,
+                     ctypes.double,
+                     ctypes.double, ctypes.int32_t.ptr.ptr.ptr.array(),
+                     test_struct, test_struct.ptr.ptr,
+                     test_func, test_func.ptr.ptr.ptr, "...");
+  assertTypeErrorMessage(() => { func("xyzzy", 1, 2, 3, 4, 5); },
+                         "can't pass the string \"xyzzy\" to argument 1 of double hypot(double, int32_t****, struct test_struct, struct test_struct**, void* (*)(int32_t), void* (****)(int32_t), ...)");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-pointer.js
@@ -0,0 +1,29 @@
+// Type conversion error should report its type.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]);
+  let struct_val = test_struct();
+
+  // constructor
+  assertTypeErrorMessage(() => { ctypes.int32_t.ptr("foo"); },
+                         "can't convert the string \"foo\" to the type ctypes.int32_t.ptr");
+
+  // value setter
+  assertTypeErrorMessage(() => { test_struct.ptr().value = "foo"; },
+                         "can't convert the string \"foo\" to the type test_struct.ptr");
+  assertTypeErrorMessage(() => { test_struct.ptr().value = {}; },
+                         "can't convert the object ({}) to the type test_struct.ptr");
+  assertTypeErrorMessage(() => { test_struct.ptr().value = [1, 2]; },
+                         "can't convert the array [1, 2] to the type test_struct.ptr");
+  assertTypeErrorMessage(() => { test_struct.ptr().value = Int8Array([1, 2]); },
+                         "can't convert the typed array ({0:1, 1:2}) to the type test_struct.ptr");
+
+  // contents setter
+  assertTypeErrorMessage(() => { ctypes.int32_t().address().contents = {}; },
+                         "can't convert the object ({}) to the type int32_t");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-primitive.js
@@ -0,0 +1,44 @@
+// Type conversion error should report its type.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  // constructor
+  assertTypeErrorMessage(() => { ctypes.int32_t("foo"); },
+                         "can't convert the string \"foo\" to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t(null); },
+                         "can't convert null to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t(undefined); },
+                         "can't convert undefined to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t({}); },
+                         "can't convert the object ({}) to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t([]); },
+                         "can't convert the array [] to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t(new Int8Array([])); },
+                         "can't convert the typed array ({}) to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t(ctypes.int32_t); },
+                         "can't convert ctypes.int32_t to the type int32_t");
+  assertTypeErrorMessage(() => { ctypes.int32_t("0xfffffffffffffffffffffff"); },
+                         "can't convert the string \"0xfffffffffffffffffffffff\" to the type int32_t");
+  if (typeof Symbol === "function") {
+    assertTypeErrorMessage(() => { ctypes.int32_t(Symbol.iterator); },
+                           "can't convert Symbol.iterator to the type int32_t");
+    assertTypeErrorMessage(() => { ctypes.int32_t(Symbol("foo")); },
+                           "can't convert Symbol(\"foo\") to the type int32_t");
+  }
+
+  // value setter
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t }]);
+  let struct_val = test_struct();
+  assertTypeErrorMessage(() => { ctypes.bool().value = struct_val; },
+                         "can't convert test_struct(0) to the type boolean");
+  assertTypeErrorMessage(() => { ctypes.char16_t().value = struct_val; },
+                         "can't convert test_struct(0) to the type char16_t");
+  assertTypeErrorMessage(() => { ctypes.int8_t().value = struct_val; },
+                         "can't convert test_struct(0) to the type int8_t");
+  assertTypeErrorMessage(() => { ctypes.double().value = struct_val; },
+                         "can't convert test_struct(0) to the type double");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-struct.js
@@ -0,0 +1,36 @@
+// Type conversion error should report its type.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.int32_t },
+                                                      { "bar": ctypes.int32_t }]);
+
+  // constructor
+  assertTypeErrorMessage(() => { new test_struct("foo"); },
+                         "can't convert the string \"foo\" to the type test_struct");
+  assertTypeErrorMessage(() => { new test_struct("foo", "x"); },
+                         "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct");
+  assertTypeErrorMessage(() => { new test_struct({ "x": "foo", "bar": 1 }); },
+                         "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct");
+  assertTypeErrorMessage(() => { new test_struct({ 0: 1, "bar": 1 }); },
+                         "property name the number 0 of the object ({0:1, bar:1}) is not a string");
+
+  // field setter
+  let struct_val = test_struct();
+  assertTypeErrorMessage(() => { struct_val.x = "foo"; },
+                         "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct");
+  assertTypeErrorMessage(() => { struct_val.bar = "foo"; },
+                         "can't convert the string \"foo\" to the 'bar' field (int32_t) of test_struct");
+
+  // value setter
+  assertTypeErrorMessage(() => { struct_val.value = { "x": "foo" }; },
+                         "property count of the object ({x:\"foo\"}) does not match to field count of the type test_struct (expected 2, got 1)");
+  assertTypeErrorMessage(() => { struct_val.value = { "x": "foo", "bar": 1 }; },
+                         "can't convert the string \"foo\" to the 'x' field (int32_t) of test_struct");
+  assertTypeErrorMessage(() => { struct_val.value = "foo"; },
+                         "can't convert the string \"foo\" to the type test_struct");
+}
+
+if (typeof ctypes === "object")
+  test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ctypes/conversion-to-primitive.js
@@ -0,0 +1,20 @@
+// Accessing `value` property of non primitive type should report its type.
+
+load(libdir + 'asserts.js');
+
+function test() {
+  let test_struct = ctypes.StructType("test_struct", [{ "x": ctypes.voidptr_t }]);
+  assertTypeErrorMessage(() => test_struct().value,
+                         ".value only works on character and numeric types, not `test_struct`");
+
+  let test_array = ctypes.ArrayType(test_struct);
+  assertTypeErrorMessage(() => test_array(10).value,
+                         ".value only works on character and numeric types, not `test_struct.array(10)`");
+
+  let test_pointer = ctypes.PointerType(test_struct);
+  assertTypeErrorMessage(() => test_pointer(10).value,
+                         ".value only works on character and numeric types, not `test_struct.ptr`");
+}
+
+if (typeof ctypes === "object")
+  test();
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -925,8 +925,48 @@ JS::CreateError(JSContext* cx, JSExnType
         js::ErrorObject::create(cx, type, stack, fileName,
                                 lineNumber, columnNumber, &rep, message));
     if (!obj)
         return false;
 
     rval.setObject(*obj);
     return true;
 }
+
+const char*
+js::ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes)
+{
+    if (val.isUndefined()) {
+        return "undefined";
+    }
+    if (val.isNull()) {
+        return "null";
+    }
+
+    RootedString str(cx, JS_ValueToSource(cx, val));
+    if (!str) {
+        JS_ClearPendingException(cx);
+        return "<<error converting value to string>>";
+    }
+
+    StringBuffer sb(cx);
+    if (val.isObject()) {
+        RootedObject valObj(cx, val.toObjectOrNull());
+        if (JS_IsArrayObject(cx, valObj)) {
+            sb.append("the array ");
+        } else if (JS_IsArrayBufferObject(valObj)) {
+            sb.append("the array buffer ");
+        } else if (JS_IsArrayBufferViewObject(valObj)) {
+            sb.append("the typed array ");
+        } else {
+            sb.append("the object ");
+        }
+    } else if (val.isNumber()) {
+        sb.append("the number ");
+    } else if (val.isString()) {
+        sb.append("the string ");
+    } else {
+        MOZ_ASSERT(val.isBoolean() || val.isSymbol());
+        return bytes.encodeLatin1(cx, str);
+    }
+    sb.append(str);
+    return bytes.encodeLatin1(cx, sb.finishString());
+}
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -124,11 +124,14 @@ class AutoClearPendingException
       : cx(cxArg)
     { }
 
     ~AutoClearPendingException() {
         JS_ClearPendingException(cx);
     }
 };
 
+extern const char*
+ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes);
+
 } // namespace js
 
 #endif /* jsexn_h */
--- a/toolkit/components/ctypes/tests/unit/test_jsctypes.js
+++ b/toolkit/components/ctypes/tests/unit/test_jsctypes.js
@@ -1161,17 +1161,17 @@ function run_char_tests(library, t, name
 
   // Test string autoconversion (and lack thereof).
   let literal = "autoconverted";
   let s = t.array()(literal);
   do_check_eq(s.readString(), literal);
   do_check_eq(s.constructor.length, literal.length + 1);
   s = t.array(50)(literal);
   do_check_eq(s.readString(), literal);
-  do_check_throws(function() { t.array(3)(literal); }, Error);
+  do_check_throws(function() { t.array(3)(literal); }, TypeError);
 
   do_check_throws(function() { t.ptr(literal); }, TypeError);
   let p = t.ptr(s);
   do_check_eq(p.readString(), literal);
 
   // Test the function call ABI for calls involving the type,
   // and check the alignment of the type against what C says.
   run_basic_abi_tests(library, t, name, toprimitive,
@@ -1253,17 +1253,17 @@ function run_char16_tests(library, t, na
 
   // Test string autoconversion (and lack thereof).
   let literal = "autoconverted";
   let s = t.array()(literal);
   do_check_eq(s.readString(), literal);
   do_check_eq(s.constructor.length, literal.length + 1);
   s = t.array(50)(literal);
   do_check_eq(s.readString(), literal);
-  do_check_throws(function() { t.array(3)(literal); }, Error);
+  do_check_throws(function() { t.array(3)(literal); }, TypeError);
 
   do_check_throws(function() { t.ptr(literal); }, TypeError);
   let p = t.ptr(s);
   do_check_eq(p.readString(), literal);
 
   // Test the function call ABI for calls involving the type,
   // and check the alignment of the type against what C says.
   run_basic_abi_tests(library, t, name, toprimitive,
@@ -1582,35 +1582,35 @@ function run_StructType_tests() {
 
   do_check_eq(s.toSource(), "s_t(4, {\"a\": 7, \"b\": 2}, 10)");
   do_check_eq(s.toSource(), s.toString());
   eval("let s2 = " + s.toSource());
   do_check_true(s2.constructor === s_t);
   do_check_eq(s.b.b, s2.b.b);
 
   // Test that structs can be set from an object using 'value'.
-  do_check_throws(function() { s.value; }, Error);
+  do_check_throws(function() { s.value; }, TypeError);
   let s_init = { "a": 2, "b": { "a": 9, "b": 5 }, "c": 13 };
   s.value = s_init;
   do_check_eq(s.b.a, 9);
   do_check_eq(s.c, 13);
   do_check_throws(function() { s.value = 5; }, TypeError);
   do_check_throws(function() { s.value = ctypes.int32_t(); }, TypeError);
-  do_check_throws(function() { s.value = {}; }, Error);
-  do_check_throws(function() { s.value = { "a": 2 }; }, Error);
+  do_check_throws(function() { s.value = {}; }, TypeError);
+  do_check_throws(function() { s.value = { "a": 2 }; }, TypeError);
   do_check_throws(function() { s.value = { "a": 2, "b": 5, "c": 10 }; }, TypeError);
   do_check_throws(function() {
     s.value = { "5": 2, "b": { "a": 9, "b": 5 }, "c": 13 };
-  }, Error);
+  }, TypeError);
   do_check_throws(function() {
     s.value = { "a": 2, "b": { "a": 9, "b": 5 }, "c": 13, "d": 17 };
-  }, Error);
+  }, TypeError);
   do_check_throws(function() {
     s.value = { "a": 2, "b": { "a": 9, "b": 5, "e": 9 }, "c": 13 };
-  }, Error);
+  }, TypeError);
 
   // Test that structs can be constructed similarly through ExplicitConvert,
   // and that the single-field case is disambiguated correctly.
   s = s_t(s_init);
   do_check_eq(s.b.a, 9);
   do_check_eq(s.c, 13);
   let v_t = ctypes.StructType("v_t", [{ "x": ctypes.int32_t }]);
   let v = v_t({ "x": 5 });
@@ -1677,17 +1677,17 @@ function run_PointerType_tests() {
   do_check_eq(p_t.toSource(),
     "ctypes.StructType(\"g_t\", [{ \"a\": ctypes.int32_t }, { \"b\": ctypes.double }]).ptr");
   do_check_true(p_t.ptr === ctypes.PointerType(p_t));
   do_check_eq(p_t.array().name, name + "*[]");
   do_check_eq(p_t.array(5).name, name + "*[5]");
 
   // Test ExplicitConvert.
   let p = p_t();
-  do_check_throws(function() { p.value; }, Error);
+  do_check_throws(function() { p.value; }, TypeError);
   do_check_eq(ptrValue(p), 0);
   do_check_throws(function() { p.contents; }, Error);
   do_check_throws(function() { p.contents = g; }, Error);
   p = p_t(5);
   do_check_eq(ptrValue(p), 5);
   p = p_t(ctypes.UInt64(10));
   do_check_eq(ptrValue(p), 10);
 
@@ -1793,20 +1793,20 @@ function run_PointerType_tests() {
         for (let k = 0; k < number_of_items; ++k) {
           do_check_eq(c_array[k], view[k]);
         }
 
         // Convert typed array to array of wrong size, ensure that it fails
         let array_type_too_large = item_type.array(number_of_items + 1);
         let array_type_too_small = item_type.array(number_of_items - 1);
 
-        do_check_throws(function() { array_type_too_large(c_arraybuffer); }, Error);
-        do_check_throws(function() { array_type_too_small(c_arraybuffer); }, Error);
-        do_check_throws(function() { array_type_too_large(view); }, Error);
-        do_check_throws(function() { array_type_too_small(view); }, Error);
+        do_check_throws(function() { array_type_too_large(c_arraybuffer); }, TypeError);
+        do_check_throws(function() { array_type_too_small(c_arraybuffer); }, TypeError);
+        do_check_throws(function() { array_type_too_large(view); }, TypeError);
+        do_check_throws(function() { array_type_too_small(view); }, TypeError);
 
         // Convert subarray of typed array to array of right size and check contents
         c_array = array_type_too_small(view.subarray(1));
         for (let k = 1; k < number_of_items; ++k) {
           do_check_eq(c_array[k - 1], view[k]);
         }
       }
     }
@@ -1879,17 +1879,17 @@ function run_FunctionType_tests() {
   do_check_eq(fp_t.toSource(),
     "ctypes.FunctionType(ctypes.default_abi, g_t).ptr");
 
   // Check that constructing a FunctionType CData directly throws.
   do_check_throws(function() { f_t(); }, Error);
 
   // Test ExplicitConvert.
   let f = fp_t();
-  do_check_throws(function() { f.value; }, Error);
+  do_check_throws(function() { f.value; }, TypeError);
   do_check_eq(ptrValue(f), 0);
   f = fp_t(5);
   do_check_eq(ptrValue(f), 5);
   f = fp_t(ctypes.UInt64(10));
   do_check_eq(ptrValue(f), 10);
 
   // Test ImplicitConvert.
   f.value = null;
@@ -2062,21 +2062,21 @@ function run_ArrayType_tests() {
   do_check_eq(c.toSource(), c.toString());
   eval("let c2 = " + c.toSource());
   do_check_eq(c2.constructor.name, "int32_t[6]");
   do_check_eq(c2.length, 6);
   do_check_eq(c2[3], c[3]);
 
   c.value = c;
   do_check_eq(c[3], 4);
-  do_check_throws(function() { c.value; }, Error);
-  do_check_throws(function() { c.value = [1, 2, 3, 4, 5]; }, Error);
-  do_check_throws(function() { c.value = [1, 2, 3, 4, 5, 6, 7]; }, Error);
+  do_check_throws(function() { c.value; }, TypeError);
+  do_check_throws(function() { c.value = [1, 2, 3, 4, 5]; }, TypeError);
+  do_check_throws(function() { c.value = [1, 2, 3, 4, 5, 6, 7]; }, TypeError);
   do_check_throws(function() { c.value = [1, 2, 7.4, 4, 5, 6]; }, TypeError);
-  do_check_throws(function() { c.value = []; }, Error);
+  do_check_throws(function() { c.value = []; }, TypeError);
 }
 
 function run_type_toString_tests() {
   var c = ctypes;
 
   // Figure out whether we can create functions with ctypes.stdcall_abi and ctypes.winapi_abi.
   var haveStdCallABI;
   try {