Bug 1439712 - CTypes string error checking, r=jonco
authorSteve Fink <sfink@mozilla.com>
Tue, 17 Apr 2018 17:06:10 -0700
changeset 467952 d56f8af6e3ab03e12fc22fd0f4b6003d50e8218e
parent 467951 321ec4ba2ea3421c45612c2bcdf8a8e0696b1b77
child 467953 be9efc84b58282ae957c674b2f9596c86a181257
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1439712
milestone61.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 1439712 - CTypes string error checking, r=jonco
js/src/ctypes/CTypes.cpp
js/src/ctypes/CTypes.h
js/src/ctypes/Library.cpp
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -929,17 +929,17 @@ static JSObject*
 MaybeUnwrapArrayWrapper(JSObject* obj)
 {
     if (IsProxy(obj) && obj->as<ProxyObject>().handler() == &CDataArrayProxyHandler::singleton)
         return obj->as<ProxyObject>().target();
     return obj;
 }
 
 static MOZ_ALWAYS_INLINE JSString*
-NewUCString(JSContext* cx, const AutoString& from)
+NewUCString(JSContext* cx, const AutoStringChars&& from)
 {
   return JS_NewUCStringCopyN(cx, from.begin(), from.length());
 }
 
 /*
  * Return a size rounded up to a multiple of a power of two.
  *
  * Note: |align| must be a power of 2.
@@ -977,17 +977,17 @@ GetErrorMessage(void* userRef, const uns
   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));
+  return bytes.encodeLatin1(cx, NewUCString(cx, str.finish()));
 }
 
 static const char*
 CTypesToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes)
 {
   if (val.isObject()) {
       RootedObject obj(cx, &val.toObject());
       if (CType::IsCType(obj) || CData::IsCDataMaybeUnwrap(&obj)) {
@@ -1190,16 +1190,18 @@ ConvError(JSContext* cx, const char* exp
       MOZ_ASSERT(!funObj);
 
       char indexStr[16];
       SprintfLiteral(indexStr, "%u", arrIndex);
 
       AutoString arrSource;
       JSAutoByteString arrBytes;
       BuildTypeSource(cx, arrObj, true, arrSource);
+      if (!arrSource)
+          return false;
       const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes);
       if (!arrStr)
         return false;
 
       JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                  CTYPESMSG_CONV_ERROR_ARRAY,
                                  valStr, indexStr, arrStr);
       break;
@@ -1210,25 +1212,29 @@ ConvError(JSContext* cx, const char* exp
       JSAutoByteString nameBytes;
       const char* nameStr = nameBytes.encodeLatin1(cx, name);
       if (!nameStr)
         return false;
 
       AutoString structSource;
       JSAutoByteString structBytes;
       BuildTypeSource(cx, arrObj, true, structSource);
+      if (!structSource)
+          return false;
       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);
+        if (!posSource)
+            return false;
         posStr = EncodeLatin1(cx, posSource, posBytes);
         if (!posStr)
           return false;
       } else {
         posStr = "";
       }
 
       JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
@@ -1247,45 +1253,51 @@ ConvError(JSContext* cx, const char* exp
     MOZ_ASSERT(funObj);
 
     char indexStr[16];
     SprintfLiteral(indexStr, "%u", argIndex + 1);
 
     AutoString funSource;
     JSAutoByteString funBytes;
     BuildFunctionTypeSource(cx, funObj, funSource);
+    if (!funSource)
+        return false;
     const char* funStr = EncodeLatin1(cx, funSource, funBytes);
     if (!funStr)
       return false;
 
     JS_ReportErrorNumberLatin1(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);
+    if (!funSource)
+        return false;
     const char* funStr = EncodeLatin1(cx, funSource, funBytes);
     if (!funStr)
       return false;
 
     JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                CTYPESMSG_CONV_ERROR_FIN, valStr, funStr);
     break;
   }
   case ConversionType::Return: {
     MOZ_ASSERT(funObj);
 
     AutoString funSource;
     JSAutoByteString funBytes;
     BuildFunctionTypeSource(cx, funObj, funSource);
+    if (!funSource)
+        return false;
     const char* funStr = EncodeLatin1(cx, funSource, funBytes);
     if (!funStr)
       return false;
 
     JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                CTYPESMSG_CONV_ERROR_RET, valStr, funStr);
     break;
   }
@@ -1307,16 +1319,18 @@ ConvError(JSContext* cx, HandleObject ex
           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);
+  if (!expectedSource)
+      return false;
   const char* expectedStr = EncodeLatin1(cx, expectedSource, expectedBytes);
   if (!expectedStr)
     return false;
 
   return ConvError(cx, expectedStr, actual, convType, funObj, argIndex,
                    arrObj, arrIndex);
 }
 
@@ -1361,16 +1375,18 @@ ArrayLengthMismatch(JSContext* cx, unsig
   char expectedLengthStr[16];
   SprintfLiteral(expectedLengthStr, "%u", expectedLength);
   char actualLengthStr[16];
   SprintfLiteral(actualLengthStr, "%u", actualLength);
 
   AutoString arrSource;
   JSAutoByteString arrBytes;
   BuildTypeSource(cx, arrObj, true, arrSource);
+  if (!arrSource)
+      return false;
   const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes);
   if (!arrStr)
     return false;
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                              CTYPESMSG_ARRAY_MISMATCH,
                              valStr, arrStr, expectedLengthStr, actualLengthStr);
   return false;
@@ -1391,16 +1407,18 @@ ArrayLengthOverflow(JSContext* cx, unsig
   char expectedLengthStr[16];
   SprintfLiteral(expectedLengthStr, "%u", expectedLength);
   char actualLengthStr[16];
   SprintfLiteral(actualLengthStr, "%u", actualLength);
 
   AutoString arrSource;
   JSAutoByteString arrBytes;
   BuildTypeSource(cx, arrObj, true, arrSource);
+  if (!arrSource)
+      return false;
   const char* arrStr = EncodeLatin1(cx, arrSource, arrBytes);
   if (!arrStr)
     return false;
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                              CTYPESMSG_ARRAY_OVERFLOW,
                              valStr, arrStr, expectedLengthStr, actualLengthStr);
   return false;
@@ -1456,16 +1474,18 @@ 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);
+    if (!posSource)
+        return false;
     posStr = EncodeLatin1(cx, posSource, posBytes);
     if (!posStr)
       return false;
   } else {
     posStr = "";
   }
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
@@ -1485,30 +1505,34 @@ FieldCountMismatch(JSContext* cx,
   JSAutoByteString valBytes;
   const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
   if (!valStr)
     return false;
 
   AutoString structSource;
   JSAutoByteString structBytes;
   BuildTypeSource(cx, structObj, true, structSource);
+  if (!structSource)
+      return false;
   const char* structStr = EncodeLatin1(cx, structSource, structBytes);
   if (!structStr)
     return false;
 
   char expectedCountStr[16];
   SprintfLiteral(expectedCountStr, "%u", expectedCount);
   char actualCountStr[16];
   SprintfLiteral(actualCountStr, "%u", actualCount);
 
   JSAutoByteString posBytes;
   const char* posStr;
   if (funObj) {
     AutoString posSource;
     BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
+    if (!posSource)
+        return false;
     posStr = EncodeLatin1(cx, posSource, posBytes);
     if (!posStr)
       return false;
   } else {
     posStr = "";
   }
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
@@ -1628,16 +1652,18 @@ FinalizerSizeError(JSContext* cx, Handle
   JSAutoByteString valBytes;
   const char* valStr = CTypesToSourceForError(cx, actual, valBytes);
   if (!valStr)
     return false;
 
   AutoString funSource;
   JSAutoByteString funBytes;
   BuildFunctionTypeSource(cx, funObj, funSource);
+  if (!funSource)
+      return false;
   const char* funStr = EncodeLatin1(cx, funSource, funBytes);
   if (!funStr)
     return false;
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                              CTYPESMSG_FIN_SIZE_ERROR, funStr, valStr);
   return false;
 }
@@ -1651,16 +1677,18 @@ FunctionArgumentLengthMismatch(JSContext
   AutoString funSource;
   JSAutoByteString funBytes;
   Value slot = JS_GetReservedSlot(funObj, SLOT_REFERENT);
   if (!slot.isUndefined() && Library::IsLibrary(&slot.toObject())) {
     BuildFunctionTypeSource(cx, funObj, funSource);
   } else {
     BuildFunctionTypeSource(cx, typeObj, funSource);
   }
+  if (!funSource)
+      return false;
   const char* funStr = EncodeLatin1(cx, funSource, funBytes);
   if (!funStr)
     return false;
 
   char expectedCountStr[16];
   SprintfLiteral(expectedCountStr, "%u", expectedCount);
   char actualCountStr[16];
   SprintfLiteral(actualCountStr, "%u", actualCount);
@@ -1804,16 +1832,18 @@ InvalidIndexRangeError(JSContext* cx, si
 static bool
 NonPrimitiveError(JSContext* cx, HandleObject typeObj)
 {
   MOZ_ASSERT(CType::IsCType(typeObj));
 
   AutoString typeSource;
   JSAutoByteString typeBytes;
   BuildTypeSource(cx, typeObj, true, typeSource);
+  if (!typeSource)
+      return false;
   const char* typeStr = EncodeLatin1(cx, typeSource, typeBytes);
   if (!typeStr)
     return false;
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                              CTYPESMSG_NON_PRIMITIVE, typeStr);
   return false;
 }
@@ -1861,16 +1891,18 @@ PropNameNonStringError(JSContext* cx, Ha
   if (!propStr)
     return false;
 
   JSAutoByteString posBytes;
   const char* posStr;
   if (funObj) {
     AutoString posSource;
     BuildConversionPosition(cx, convType, funObj, argIndex, posSource);
+    if (!posSource)
+        return false;
     posStr = EncodeLatin1(cx, posSource, posBytes);
     if (!posStr)
       return false;
   } else {
     posStr = "";
   }
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
@@ -1913,44 +1945,47 @@ TypeOverflow(JSContext* cx, const char* 
 }
 
 static bool
 UndefinedSizeCastError(JSContext* cx, HandleObject targetTypeObj)
 {
   AutoString targetTypeSource;
   JSAutoByteString targetTypeBytes;
   BuildTypeSource(cx, targetTypeObj, true, targetTypeSource);
-  const char* targetTypeStr = EncodeLatin1(cx, targetTypeSource,
-                                           targetTypeBytes);
+  if (!targetTypeSource)
+      return false;
+  const char* targetTypeStr = EncodeLatin1(cx, targetTypeSource, targetTypeBytes);
   if (!targetTypeStr)
     return false;
 
   JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                              CTYPESMSG_UNDEFINED_SIZE_CAST, targetTypeStr);
   return false;
 }
 
 static bool
 SizeMismatchCastError(JSContext* cx,
                       HandleObject sourceTypeObj, HandleObject targetTypeObj,
                       size_t sourceSize, size_t targetSize)
 {
   AutoString sourceTypeSource;
   JSAutoByteString sourceTypeBytes;
   BuildTypeSource(cx, sourceTypeObj, true, sourceTypeSource);
-  const char* sourceTypeStr = EncodeLatin1(cx, sourceTypeSource,
-                                           sourceTypeBytes);
+  if (!sourceTypeSource)
+      return false;
+  const char* sourceTypeStr = EncodeLatin1(cx, sourceTypeSource, sourceTypeBytes);
   if (!sourceTypeStr)
     return false;
 
   AutoString targetTypeSource;
   JSAutoByteString targetTypeBytes;
   BuildTypeSource(cx, targetTypeObj, true, targetTypeSource);
-  const char* targetTypeStr = EncodeLatin1(cx, targetTypeSource,
-                                           targetTypeBytes);
+  if (!targetTypeSource)
+      return false;
+  const char* targetTypeStr = EncodeLatin1(cx, targetTypeSource, targetTypeBytes);
   if (!targetTypeStr)
     return false;
 
   char sourceSizeStr[16];
   char targetSizeStr[16];
   SprintfLiteral(sourceSizeStr, "%zu", sourceSize);
   SprintfLiteral(targetSizeStr, "%zu", targetSize);
 
@@ -3187,19 +3222,19 @@ jsvalToPtrExplicit(JSContext* cx, Handle
       // Make sure the integer fits in the alotted precision.
       *result = uintptr_t(i);
       return uint64_t(*result) == i;
     }
   }
   return false;
 }
 
-template<class IntegerType, class CharType, size_t N, class AP>
+template<class IntegerType, class CharType, size_t N>
 void
-IntegerToString(IntegerType i, int radix, mozilla::Vector<CharType, N, AP>& result)
+IntegerToString(IntegerType i, int radix, StringBuilder<CharType, N>& result)
 {
   JS_STATIC_ASSERT(numeric_limits<IntegerType>::is_exact);
 
   // The buffer must be big enough for all the bits of IntegerType to fit,
   // in base-2, including '-'.
   CharType buffer[sizeof(IntegerType) * 8 + 1];
   CharType* end = buffer + sizeof(buffer) / sizeof(CharType);
   CharType* cp = end;
@@ -4069,25 +4104,27 @@ BuildTypeName(JSContext* cx, JSObject* t
   // If prepending the base type name directly would splice two
   // identifiers, insert a space.
   if (IsAsciiAlpha(result[0]) || result[0] == '_')
     PrependString(cx, result, " ");
 
   // Stick the base type and derived type parts together.
   JSString* baseName = CType::GetName(cx, typeObj);
   PrependString(cx, result, baseName);
-  return NewUCString(cx, result);
+  if (!result)
+      return nullptr;
+  return NewUCString(cx, result.finish());
 }
 
 // Given a CType 'typeObj', generate a string 'result' such that 'eval(result)'
 // would construct the same CType. If 'makeShort' is true, assume that any
 // StructType 't' is bound to an in-scope variable of name 't.name', and use
 // that variable in place of generating a string to construct the type 't'.
 // (This means the type comparison function CType::TypesEqual will return true
-// when comparing the input and output of BuildTypeSource, since struct
+// when comparing the input and output of AppendTypeSource, since struct
 // equality is determined by strict JSObject pointer equality.)
 static void
 BuildTypeSource(JSContext* cx,
                 JSObject* typeObj_,
                 bool makeShort,
                 AutoString& result)
 {
   RootedObject typeObj(cx, typeObj_);
@@ -4229,17 +4266,17 @@ BuildTypeSource(JSContext* cx,
 // the same CType and containing the same binary value. This assumes that any
 // StructType 't' is bound to an in-scope variable of name 't.name'. (This means
 // the type comparison function CType::TypesEqual will return true when
 // comparing the types, since struct equality is determined by strict JSObject
 // pointer equality.) Further, if 'isImplicit' is true, ensure that the
 // resulting string can ImplicitConvert successfully if passed to another data
 // constructor. (This is important when called recursively, since fields of
 // structs and arrays are converted with ImplicitConvert.)
-static bool
+static MOZ_MUST_USE bool
 BuildDataSource(JSContext* cx,
                 HandleObject typeObj,
                 void* data,
                 bool isImplicit,
                 AutoString& result)
 {
   TypeCode type = CType::GetTypeCode(typeObj);
   switch (type) {
@@ -4255,22 +4292,22 @@ BuildDataSource(JSContext* cx,
     IntegerToString(*static_cast<type*>(data), 10, result);                    \
     break;
   CTYPES_FOR_EACH_INT_TYPE(INTEGRAL_CASE)
 #undef INTEGRAL_CASE
 #define WRAPPED_INT_CASE(name, type, ffiType)                                  \
   case TYPE_##name:                                                            \
     /* Serialize as a wrapped decimal integer. */                              \
     if (!numeric_limits<type>::is_signed)                                      \
-      AppendString(cx, result, "ctypes.UInt64(\"");                                \
+      AppendString(cx, result, "ctypes.UInt64(\"");                            \
     else                                                                       \
-      AppendString(cx, result, "ctypes.Int64(\"");                                 \
+      AppendString(cx, result, "ctypes.Int64(\"");                             \
                                                                                \
     IntegerToString(*static_cast<type*>(data), 10, result);                    \
-    AppendString(cx, result, "\")");                                               \
+    AppendString(cx, result, "\")");                                           \
     break;
   CTYPES_FOR_EACH_WRAPPED_INT_TYPE(WRAPPED_INT_CASE)
 #undef WRAPPED_INT_CASE
 #define FLOAT_CASE(name, type, ffiType)                                        \
   case TYPE_##name: {                                                          \
     /* Serialize as a primitive double. */                                     \
     double fp = *static_cast<type*>(data);                                     \
     ToCStringBuf cbuf;                                                         \
@@ -4994,17 +5031,19 @@ CType::ToString(JSContext* cx, unsigned 
 
   // Create the appropriate string depending on whether we're sCTypeClass or
   // sCTypeProtoClass.
   JSString* result;
   if (CType::IsCType(obj)) {
     AutoString type;
     AppendString(cx, type, "type ");
     AppendString(cx, type, GetName(cx, obj));
-    result = NewUCString(cx, type);
+    if (!type)
+        return false;
+    result = NewUCString(cx, type.finish());
   }
   else {
     result = JS_NewStringCopyZ(cx, "[CType proto object]");
   }
   if (!result)
     return false;
 
   args.rval().setString(result);
@@ -5024,17 +5063,19 @@ CType::ToSource(JSContext* cx, unsigned 
   }
 
   // Create the appropriate string depending on whether we're sCTypeClass or
   // sCTypeProtoClass.
   JSString* result;
   if (CType::IsCType(obj)) {
     AutoString source;
     BuildTypeSource(cx, obj, false, source);
-    result = NewUCString(cx, source);
+    if (!source)
+        return false;
+    result = NewUCString(cx, source.finish());
   } else {
     result = JS_NewStringCopyZ(cx, "[CType proto object]");
   }
   if (!result)
     return false;
 
   args.rval().setString(result);
   return true;
@@ -8057,21 +8098,22 @@ CData::GetSourceString(JSContext* cx, Ha
   // 't.ptr' for pointers;
   // 't.array([n])' for arrays;
   // 'n' for structs, where n = t.name, the struct's name. (We assume this is
   // bound to a variable in the current scope.)
   AutoString source;
   BuildTypeSource(cx, typeObj, true, source);
   AppendString(cx, source, "(");
   if (!BuildDataSource(cx, typeObj, data, false, source))
-    return nullptr;
-
+    source.handle(false);
   AppendString(cx, source, ")");
-
-  return NewUCString(cx, source);
+  if (!source)
+    return nullptr;
+
+  return NewUCString(cx, source.finish());
 }
 
 bool
 CData::ToSource(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   if (args.length() != 0) {
     return ArgumentLengthError(cx, "CData.prototype.toSource", "no", "s");
@@ -8160,17 +8202,19 @@ CDataFinalizer::Methods::ToSource(JSCont
     RootedObject typeObj(cx, valCodePtrType.toObjectOrNull());
     JSString* srcDispose = CData::GetSourceString(cx, typeObj, &(p->code));
     if (!srcDispose) {
       return false;
     }
 
     AppendString(cx, source, srcDispose);
     AppendString(cx, source, ")");
-    strMessage = NewUCString(cx, source);
+    if (!source)
+        return false;
+    strMessage = NewUCString(cx, source.finish());
   }
 
   if (!strMessage) {
     // This is a memory issue, no error message
     return false;
   }
 
   args.rval().setString(strMessage);
@@ -8740,17 +8784,19 @@ Int64Base::ToString(JSContext* cx,
 
   AutoString intString;
   if (isUnsigned) {
     IntegerToString(GetInt(obj), radix, intString);
   } else {
     IntegerToString(static_cast<int64_t>(GetInt(obj)), radix, intString);
   }
 
-  JSString* result = NewUCString(cx, intString);
+  if (!intString)
+      return false;
+  JSString* result = NewUCString(cx, intString.finish());
   if (!result)
     return false;
 
   args.rval().setString(result);
   return true;
 }
 
 bool
@@ -8771,18 +8817,20 @@ Int64Base::ToSource(JSContext* cx,
   if (isUnsigned) {
     AppendString(cx, source, "ctypes.UInt64(\"");
     IntegerToString(GetInt(obj), 10, source);
   } else {
     AppendString(cx, source, "ctypes.Int64(\"");
     IntegerToString(static_cast<int64_t>(GetInt(obj)), 10, source);
   }
   AppendString(cx, source, "\")");
-
-  JSString* result = NewUCString(cx, source);
+  if (!source)
+    return false;
+
+  JSString* result = NewUCString(cx, source.finish());
   if (!result)
     return false;
 
   args.rval().setString(result);
   return true;
 }
 
 bool
--- a/js/src/ctypes/CTypes.h
+++ b/js/src/ctypes/CTypes.h
@@ -21,92 +21,171 @@
 
 namespace js {
 namespace ctypes {
 
 /*******************************************************************************
 ** Utility classes
 *******************************************************************************/
 
-// String and AutoString classes, based on Vector.
-typedef Vector<char16_t,  0, SystemAllocPolicy> String;
-typedef Vector<char16_t, 64, SystemAllocPolicy> AutoString;
-typedef Vector<char,      0, SystemAllocPolicy> CString;
-typedef Vector<char,     64, SystemAllocPolicy> AutoCString;
+// CTypes builds a number of strings. StringBuilder allows repeated appending
+// with a single error check at the end. Only the Vector methods required for
+// building the string are exposed.
+
+template <class CharT, size_t N>
+class StringBuilder {
+  Vector<CharT, N, SystemAllocPolicy> v;
+
+  // Have any (OOM) errors been encountered while constructing this string?
+  bool errored { false };
+
+#ifdef DEBUG
+  // Have we finished building this string?
+  bool finished { false };
+
+  // Did we check for errors?
+  mutable bool checked { false };
+#endif
+
+ public:
+
+  explicit operator bool() const {
+#ifdef DEBUG
+    checked = true;
+#endif
+    return !errored;
+  }
+
+  // Handle the result of modifying the string, by remembering the persistent
+  // errored status.
+  bool handle(bool result) {
+    MOZ_ASSERT(!finished);
+    if (!result)
+      errored = true;
+    return result;
+  }
+
+  bool resize(size_t n) {
+    return handle(v.resize(n));
+  }
+
+  CharT& operator[](size_t index) { return v[index]; }
+  const CharT& operator[](size_t index) const { return v[index]; }
+  size_t length() const { return v.length(); }
+
+  template<typename U> MOZ_MUST_USE bool append(U&& u) {
+    return handle(v.append(u));
+  }
+
+  template<typename U> MOZ_MUST_USE bool append(const U* begin, const U* end) {
+    return handle(v.append(begin, end));
+  }
+
+  template<typename U> MOZ_MUST_USE bool append(const U* begin, size_t len) {
+    return handle(v.append(begin, len));
+  }
+
+  CharT* begin() {
+    MOZ_ASSERT(!finished);
+    return v.begin();
+  }
+
+  // finish() produces the results of the string building, and is required as
+  // the last thing before the string contents are used. The StringBuilder must
+  // be checked for errors before calling this, however.
+  Vector<CharT, N, SystemAllocPolicy>&& finish() {
+    MOZ_ASSERT(!errored);
+    MOZ_ASSERT(!finished);
+    MOZ_ASSERT(checked);
+#ifdef DEBUG
+    finished = true;
+#endif
+    return mozilla::Move(v);
+  }
+};
+
+// Note that these strings do not have any inline storage, because we use move
+// constructors to pass the data around and inline storage would necessitate
+// copying.
+typedef StringBuilder<char16_t, 0> AutoString;
+typedef StringBuilder<char,     0> AutoCString;
+
+typedef Vector<char16_t, 0, SystemAllocPolicy> AutoStringChars;
+typedef Vector<char,     0, SystemAllocPolicy> AutoCStringChars;
 
 // Convenience functions to append, insert, and compare Strings.
-template <class T, size_t N, class AP, size_t ArrayLength>
+template <class T, size_t N, size_t ArrayLength>
 void
-AppendString(JSContext* cx, mozilla::Vector<T, N, AP>& v, const char (&array)[ArrayLength])
+AppendString(JSContext* cx, StringBuilder<T, N>& v, const char (&array)[ArrayLength])
 {
   // Don't include the trailing '\0'.
   size_t alen = ArrayLength - 1;
   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>
+template <class T, size_t N>
 void
-AppendChars(mozilla::Vector<T, N, AP>& v, const char c, size_t count)
+AppendChars(StringBuilder<T, N>& 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>
+template <class T, size_t N>
 void
-AppendUInt(mozilla::Vector<T, N, AP>& v, unsigned n)
+AppendUInt(StringBuilder<T, N>& v, unsigned n)
 {
   char array[16];
   size_t alen = SprintfLiteral(array, "%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(JSContext* cx, mozilla::Vector<T, N, AP>& v, mozilla::Vector<T, M, AP>& w)
+AppendString(JSContext* cx, StringBuilder<T, N>& v, mozilla::Vector<T, M, AP>& w)
 {
   if (!v.append(w.begin(), w.length()))
     return;
 }
 
-template <size_t N, class AP>
+template <size_t N>
 void
-AppendString(JSContext* cx, mozilla::Vector<char16_t, N, AP>& v, JSString* str)
+AppendString(JSContext* cx, StringBuilder<char16_t, N>& v, JSString* str)
 {
   MOZ_ASSERT(str);
   JSLinearString* linear = str->ensureLinear(cx);
   if (!linear)
     return;
   JS::AutoCheckCannotGC nogc;
   if (linear->hasLatin1Chars()) {
     if (!v.append(linear->latin1Chars(nogc), linear->length()))
       return;
   } else {
     if (!v.append(linear->twoByteChars(nogc), linear->length()))
       return;
   }
 }
 
-template <size_t N, class AP>
+template <size_t N>
 void
-AppendString(JSContext* cx, mozilla::Vector<char, N, AP>& v, JSString* str)
+AppendString(JSContext* cx, StringBuilder<char, N>& v, JSString* str)
 {
   MOZ_ASSERT(str);
   size_t vlen = v.length();
   size_t alen = str->length();
   if (!v.resize(vlen + alen))
     return;
 
   JSLinearString* linear = str->ensureLinear(cx);
@@ -120,37 +199,37 @@ AppendString(JSContext* cx, mozilla::Vec
       v[i + vlen] = char(chars[i]);
   } else {
     const char16_t* chars = linear->twoByteChars(nogc);
     for (size_t i = 0; i < alen; ++i)
       v[i + vlen] = char(chars[i]);
   }
 }
 
-template <class T, size_t N, class AP, size_t ArrayLength>
+template <class T, size_t N, size_t ArrayLength>
 void
-PrependString(JSContext* cx, mozilla::Vector<T, N, AP>& v, const char (&array)[ArrayLength])
+PrependString(JSContext* cx, StringBuilder<T, N>& v, const char (&array)[ArrayLength])
 {
   // Don't include the trailing '\0'.
   size_t alen = ArrayLength - 1;
   size_t vlen = v.length();
   if (!v.resize(vlen + alen))
     return;
 
   // Move vector data forward. This is safe since we've already resized.
   memmove(v.begin() + alen, v.begin(), vlen * sizeof(T));
 
   // Copy data to insert.
   for (size_t i = 0; i < alen; ++i)
     v[i] = array[i];
 }
 
-template <size_t N, class AP>
+template <size_t N>
 void
-PrependString(JSContext* cx, mozilla::Vector<char16_t, N, AP>& v, JSString* str)
+PrependString(JSContext* cx, StringBuilder<char16_t, N>& v, JSString* str)
 {
   MOZ_ASSERT(str);
   size_t vlen = v.length();
   size_t alen = str->length();
   if (!v.resize(vlen + alen))
     return;
 
   JSLinearString* linear = str->ensureLinear(cx);
--- a/js/src/ctypes/Library.cpp
+++ b/js/src/ctypes/Library.cpp
@@ -70,19 +70,21 @@ Library::Name(JSContext* cx, unsigned ar
     JS_ReportErrorASCII(cx, "name argument must be a string");
     return false;
   }
 
   AutoString resultString;
   AppendString(cx, resultString, DLL_PREFIX);
   AppendString(cx, resultString, str);
   AppendString(cx, resultString, DLL_SUFFIX);
+  if (!resultString)
+    return false;
+  auto resultStr = resultString.finish();
 
-  JSString* result = JS_NewUCStringCopyN(cx, resultString.begin(),
-                                         resultString.length());
+  JSString* result = JS_NewUCStringCopyN(cx, resultStr.begin(), resultStr.length());
   if (!result)
     return false;
 
   args.rval().setString(result);
   return true;
 }
 
 JSObject*
@@ -336,31 +338,35 @@ Library::Declare(JSContext* cx, unsigned
   void* data;
   PRFuncPtr fnptr;
   RootedString nameStr(cx, args[0].toString());
   AutoCString symbol;
   if (isFunction) {
     // Build the symbol, with mangling if necessary.
     FunctionType::BuildSymbolName(cx, nameStr, fnObj, symbol);
     AppendString(cx, symbol, "\0");
+    if (!symbol)
+      return false;
 
     // Look up the function symbol.
-    fnptr = PR_FindFunctionSymbol(library, symbol.begin());
+    fnptr = PR_FindFunctionSymbol(library, symbol.finish().begin());
     if (!fnptr) {
       JS_ReportErrorASCII(cx, "couldn't find function symbol in library");
       return false;
     }
     data = &fnptr;
 
   } else {
     // 'typeObj' is another data type. Look up the data symbol.
     AppendString(cx, symbol, nameStr);
     AppendString(cx, symbol, "\0");
+    if (!symbol)
+      return false;
 
-    data = PR_FindSymbol(library, symbol.begin());
+    data = PR_FindSymbol(library, symbol.finish().begin());
     if (!data) {
       JS_ReportErrorASCII(cx, "couldn't find symbol in library");
       return false;
     }
   }
 
   RootedObject result(cx, CData::Create(cx, typeObj, obj, data, isFunction));
   if (!result)