Bug 1533636 - Introduce fill-and-terminate functions for filling dest chars from source chars and null-terminating, when the filling will not lose information. r=tcampbell
authorJeff Walden <jwalden@mit.edu>
Wed, 06 Mar 2019 03:24:12 -0800
changeset 521367 8ffeb0cdf0e58989498414a1f9b7c22495f7f998
parent 521366 0ad3e7b4f75e90c05ab0ee975dd17a9b7d43ce5d
child 521368 b7c332bee855b657db8f0bcbba9dc98465891a1d
push id10866
push usernerli@mozilla.com
push dateTue, 12 Mar 2019 18:59:09 +0000
treeherdermozilla-beta@445c24a51727 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1533636
milestone67.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 1533636 - Introduce fill-and-terminate functions for filling dest chars from source chars and null-terminating, when the filling will not lose information. r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D22653
js/src/vm/StringType.cpp
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -2,27 +2,29 @@
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/StringType-inl.h"
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Unused.h"
 
-#include <algorithm>
+#include <algorithm>  // std::{all_of,copy_n,enable_if,is_const,move}
+#include <type_traits>  // std::is_unsigned
 
 #include "jsfriendapi.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "gc/GCInternals.h"
 #include "gc/Marking.h"
 #include "gc/Nursery.h"
 #include "js/CharacterEncoding.h"
@@ -35,16 +37,17 @@
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/JSContext-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/Realm-inl.h"
 
 using namespace js;
 
 using mozilla::ArrayEqual;
+using mozilla::AssertedCast;
 using mozilla::IsAsciiDigit;
 using mozilla::IsNegativeZero;
 using mozilla::IsSame;
 using mozilla::PodCopy;
 using mozilla::RangedPtr;
 using mozilla::RoundUpPow2;
 using mozilla::Unused;
 
@@ -791,34 +794,88 @@ JSString* js::ConcatStrings(
 }
 
 template JSString* js::ConcatStrings<CanGC>(JSContext* cx, HandleString left,
                                             HandleString right);
 
 template JSString* js::ConcatStrings<NoGC>(JSContext* cx, JSString* const& left,
                                            JSString* const& right);
 
+/**
+ * Copy |src[0..length]| to |dest[0..length]| when copying doesn't narrow and
+ * therefore can't lose information.
+ */
+template <typename Dest, typename Source>
+static void FillAndTerminate(Dest* dest, Source src, size_t length) {
+  static_assert(sizeof(src[length]) <= sizeof(dest[length]),
+                "source → destination conversion must not narrow");
+
+  for (size_t i = 0; i < length; i++) {
+    auto srcChar = src[i];
+    static_assert(std::is_unsigned<decltype(srcChar)>::value &&
+                  std::is_unsigned<Dest>::value,
+                  "source/destination characters are unsigned for simplicity");
+    *dest++ = srcChar;
+  }
+
+  *dest = '\0';
+}
+
+template <typename Dest, typename Src,
+          typename = typename std::enable_if<!std::is_const<Src>::value>::type>
+static void FillAndTerminate(Dest* dest, Src* src, size_t length) {
+  FillAndTerminate(dest, const_cast<const Src*>(src), length);
+}
+
+/**
+ * Copy |src[0..length]| to |dest[0..length]| when copying *does* narrow, but
+ * the user guarantees every runtime |src[i]| value can be stored without change
+ * of value in |dest[i]|.
+ */
+template <typename Dest, typename Source>
+static void FillFromCompatibleAndTerminate(Dest* dest, Source src,
+                                           size_t length) {
+  static_assert(sizeof(src[length]) > sizeof(dest[length]),
+                "source → destination conversion must be narrowing");
+
+  for (size_t i = 0; i < length; i++) {
+    auto srcChar = src[i];
+    static_assert(std::is_unsigned<decltype(srcChar)>::value &&
+                  std::is_unsigned<Dest>::value,
+                  "source/destination characters are unsigned for simplicity");
+    *dest++ = AssertedCast<Dest>(src[i]);
+  }
+
+  *dest = '\0';
+}
+
+template <typename Dest, typename Src,
+          typename = typename std::enable_if<!std::is_const<Src>::value>::type>
+static void FillFromCompatibleAndTerminate(Dest* dest, Src* src,
+                                           size_t length) {
+  FillFromCompatibleAndTerminate(dest, const_cast<const Src*>(src), length);
+}
+
 template <typename CharT>
 JSFlatString* JSDependentString::undependInternal(JSContext* cx) {
   size_t n = length();
   auto s = cx->make_pod_array<CharT>(n + 1);
   if (!s) {
     return nullptr;
   }
 
   if (!isTenured()) {
     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s.get())) {
       ReportOutOfMemory(cx);
       return nullptr;
     }
   }
 
   AutoCheckCannotGC nogc;
-  PodCopy(s.get(), nonInlineChars<CharT>(nogc), n);
-  s[n] = '\0';
+  FillAndTerminate(s.get(), nonInlineChars<CharT>(nogc), n);
   setNonInlineChars<CharT>(s.release());
 
   /*
    * Transform *this into an undepended string so 'base' will remain rooted
    * for the benefit of any other dependent string that depends on *this.
    */
   if (IsSame<CharT, Latin1Char>::value) {
     setLengthAndFlags(n, UNDEPENDED_FLAGS | LATIN1_CHARS_BIT);
@@ -1321,53 +1378,50 @@ T* AutoStableStringChars::allocOwnChars(
 
 bool AutoStableStringChars::copyAndInflateLatin1Chars(
     JSContext* cx, HandleLinearString linearString) {
   char16_t* chars = allocOwnChars<char16_t>(cx, linearString->length() + 1);
   if (!chars) {
     return false;
   }
 
-  CopyAndInflateChars(chars, linearString->rawLatin1Chars(),
-                      linearString->length());
-  chars[linearString->length()] = 0;
+  FillAndTerminate(chars, linearString->rawLatin1Chars(),
+                   linearString->length());
 
   state_ = TwoByte;
   twoByteChars_ = chars;
   s_ = linearString;
   return true;
 }
 
 bool AutoStableStringChars::copyLatin1Chars(JSContext* cx,
                                             HandleLinearString linearString) {
   size_t length = linearString->length();
   JS::Latin1Char* chars = allocOwnChars<JS::Latin1Char>(cx, length + 1);
   if (!chars) {
     return false;
   }
 
-  PodCopy(chars, linearString->rawLatin1Chars(), length);
-  chars[length] = 0;
+  FillAndTerminate(chars, linearString->rawLatin1Chars(), length);
 
   state_ = Latin1;
   latin1Chars_ = chars;
   s_ = linearString;
   return true;
 }
 
 bool AutoStableStringChars::copyTwoByteChars(JSContext* cx,
                                              HandleLinearString linearString) {
   size_t length = linearString->length();
   char16_t* chars = allocOwnChars<char16_t>(cx, length + 1);
   if (!chars) {
     return false;
   }
 
-  PodCopy(chars, linearString->rawTwoByteChars(), length);
-  chars[length] = 0;
+  FillAndTerminate(chars, linearString->rawTwoByteChars(), length);
 
   state_ = TwoByte;
   twoByteChars_ = chars;
   s_ = linearString;
   return true;
 }
 
 JSFlatString* JSString::ensureFlat(JSContext* cx) {
@@ -1397,18 +1451,17 @@ JSFlatString* JSExternalString::ensureFl
       ReportOutOfMemory(cx);
       return nullptr;
     }
   }
 
   // Copy the chars before finalizing the string.
   {
     AutoCheckCannotGC nogc;
-    PodCopy(s.get(), nonInlineChars<char16_t>(nogc), n);
-    s[n] = '\0';
+    FillAndTerminate(s.get(), nonInlineChars<char16_t>(nogc), n);
   }
 
   // Release the external chars.
   finalize(cx->runtime()->defaultFreeOp());
 
   // Transform the string into a non-external, flat string. Note that the
   // resulting string will still be in an AllocKind::EXTERNAL_STRING arena,
   // but will no longer be an external string.
@@ -1491,21 +1544,18 @@ static MOZ_ALWAYS_INLINE JSInlineString*
     JSContext* cx, mozilla::Range<const char16_t> chars) {
   size_t len = chars.length();
   Latin1Char* storage;
   JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage);
   if (!str) {
     return nullptr;
   }
 
-  for (size_t i = 0; i < len; i++) {
-    MOZ_ASSERT(chars[i] <= JSString::MAX_LATIN1_CHAR);
-    storage[i] = Latin1Char(chars[i]);
-  }
-  storage[len] = '\0';
+  MOZ_ASSERT(CanStoreCharsAsLatin1(chars.begin().get(), len));
+  FillFromCompatibleAndTerminate(storage, chars, len);
   return str;
 }
 
 template <AllowGC allowGC>
 static JSFlatString* NewStringDeflated(JSContext* cx, const char16_t* s,
                                        size_t n) {
   if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) {
     return str;
@@ -1516,21 +1566,18 @@ static JSFlatString* NewStringDeflated(J
         cx, mozilla::Range<const char16_t>(s, n));
   }
 
   auto news = cx->make_pod_array<Latin1Char>(n + 1);
   if (!news) {
     return nullptr;
   }
 
-  for (size_t i = 0; i < n; i++) {
-    MOZ_ASSERT(s[i] <= JSString::MAX_LATIN1_CHAR);
-    news[i] = Latin1Char(s[i]);
-  }
-  news[n] = '\0';
+  MOZ_ASSERT(CanStoreCharsAsLatin1(s, n));
+  FillFromCompatibleAndTerminate(news.get(), s, n);
 
   JSFlatString* str = JSFlatString::new_<allowGC>(cx, news.get(), n);
   if (!str) {
     return nullptr;
   }
 
   mozilla::Unused << news.release();
   return str;
@@ -1677,18 +1724,17 @@ JSFlatString* NewStringCopyNDontDeflate(
   auto news = cx->make_pod_array<CharT>(n + 1);
   if (!news) {
     if (!allowGC) {
       cx->recoverFromOutOfMemory();
     }
     return nullptr;
   }
 
-  PodCopy(news.get(), s, n);
-  news[n] = 0;
+  FillAndTerminate(news.get(), s, n);
 
   JSFlatString* str = JSFlatString::new_<allowGC>(cx, news.get(), n);
   if (!str) {
     return nullptr;
   }
 
   mozilla::Unused << news.release();
   return str;
@@ -2088,18 +2134,17 @@ UniqueChars js::EncodeLatin1(JSContext* 
   }
 
   size_t len = str->length();
   Latin1Char* buf = cx->pod_malloc<Latin1Char>(len + 1);
   if (!buf) {
     return nullptr;
   }
 
-  mozilla::PodCopy(buf, linear->latin1Chars(nogc), len);
-  buf[len] = '\0';
+  FillAndTerminate(buf, linear->latin1Chars(nogc), len);
   return UniqueChars(reinterpret_cast<char*>(buf));
 }
 
 UniqueChars js::EncodeAscii(JSContext* cx, JSString* str) {
   JSLinearString* linear = str->ensureLinear(cx);
   if (!linear) {
     return nullptr;
   }