string_bytes: fix unaligned write in UCS2
authorFedor Indutny <fedor@indutny.com>
Thu, 20 Aug 2015 23:57:14 +0000 (16:57 -0700)
committerFedor Indutny <fedor@indutny.com>
Fri, 21 Aug 2015 19:26:03 +0000 (12:26 -0700)
Support unaligned output buffer when writing out UCS2 in
`StringBytes::Write`.

Fix: https://github.com/nodejs/node/issues/2457
PR-URL: https://github.com/nodejs/node/pull/2480
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
src/string_bytes.cc
src/string_bytes.h

index 0abdbf8..3d568c6 100644 (file)
@@ -293,6 +293,46 @@ bool StringBytes::GetExternalParts(Isolate* isolate,
 }
 
 
+size_t StringBytes::WriteUCS2(char* buf,
+                              size_t buflen,
+                              size_t nbytes,
+                              const char* data,
+                              Local<String> str,
+                              int flags,
+                              size_t* chars_written) {
+  uint16_t* const dst = reinterpret_cast<uint16_t*>(buf);
+
+  size_t max_chars = (buflen / sizeof(*dst));
+  size_t nchars;
+  size_t alignment = reinterpret_cast<uintptr_t>(dst) % sizeof(*dst);
+  if (alignment == 0) {
+    nchars = str->Write(dst, 0, max_chars, flags);
+    *chars_written = nchars;
+    return nchars * sizeof(*dst);
+  }
+
+  uint16_t* aligned_dst =
+      reinterpret_cast<uint16_t*>(buf + sizeof(*dst) - alignment);
+  ASSERT_EQ(reinterpret_cast<uintptr_t>(aligned_dst) % sizeof(*dst), 0);
+
+  // Write all but the last char
+  nchars = str->Write(aligned_dst, 0, max_chars - 1, flags);
+
+  // Shift everything to unaligned-left
+  memmove(dst, aligned_dst, nchars * sizeof(*dst));
+
+  // One more char to be written
+  uint16_t last;
+  if (nchars == max_chars - 1 && str->Write(&last, nchars, 1, flags) != 0) {
+    memcpy(buf + nchars * sizeof(*dst), &last, sizeof(last));
+    nchars++;
+  }
+
+  *chars_written = nchars;
+  return nchars * sizeof(*dst);
+}
+
+
 size_t StringBytes::Write(Isolate* isolate,
                           char* buf,
                           size_t buflen,
@@ -334,26 +374,40 @@ size_t StringBytes::Write(Isolate* isolate,
       break;
 
     case UCS2: {
-      uint16_t* const dst = reinterpret_cast<uint16_t*>(buf);
       size_t nchars;
+
       if (is_extern && !str->IsOneByte()) {
         memcpy(buf, data, nbytes);
-        nchars = nbytes / sizeof(*dst);
+        nchars = nbytes / sizeof(uint16_t);
       } else {
-        nchars = buflen / sizeof(*dst);
-        nchars = str->Write(dst, 0, nchars, flags);
-        nbytes = nchars * sizeof(*dst);
+        nbytes = WriteUCS2(buf, buflen, nbytes, data, str, flags, &nchars);
       }
-      if (IsBigEndian()) {
-        // Node's "ucs2" encoding wants LE character data stored in
-        // the Buffer, so we need to reorder on BE platforms.  See
-        // http://nodejs.org/api/buffer.html regarding Node's "ucs2"
-        // encoding specification
+      if (chars_written != nullptr)
+        *chars_written = nchars;
+
+      if (!IsBigEndian())
+        break;
+
+      // Node's "ucs2" encoding wants LE character data stored in
+      // the Buffer, so we need to reorder on BE platforms.  See
+      // http://nodejs.org/api/buffer.html regarding Node's "ucs2"
+      // encoding specification
+
+      const bool is_aligned =
+          reinterpret_cast<uintptr_t>(buf) % sizeof(uint16_t);
+      if (is_aligned) {
+        uint16_t* const dst = reinterpret_cast<uint16_t*>(buf);
         for (size_t i = 0; i < nchars; i++)
           dst[i] = dst[i] << 8 | dst[i] >> 8;
+        break;
+      }
+
+      ASSERT_EQ(sizeof(uint16_t), 2);
+      for (size_t i = 0; i < nchars; i++) {
+        char tmp = buf[i * 2];
+        buf[i * 2] = buf[i * 2 + 1];
+        buf[i * 2 + 1] = tmp;
       }
-      if (chars_written != nullptr)
-        *chars_written = nchars;
       break;
     }
 
index 2fcfeda..7c044eb 100644 (file)
@@ -151,6 +151,15 @@ class StringBytes {
       enum encoding encoding) {
     return Encode(v8::Isolate::GetCurrent(), buf, buflen, encoding);
   })
+
+ private:
+  static size_t WriteUCS2(char* buf,
+                          size_t buflen,
+                          size_t nbytes,
+                          const char* data,
+                          v8::Local<v8::String> str,
+                          int flags,
+                          size_t* chars_written);
 };
 
 }  // namespace node