--- a/servo/support/gecko/nsstring/src/conversions.rs
+++ b/servo/support/gecko/nsstring/src/conversions.rs
@@ -1,14 +1,15 @@
/* 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/. */
extern crate encoding_rs;
+use super::BulkWriteOk;
use super::nsAString;
use super::nsACString;
use super::nsCStringLike;
use super::Latin1StringLike;
use super::Gecko_FallibleAssignCString;
use conversions::encoding_rs::mem::*;
use conversions::encoding_rs::Encoding;
@@ -40,29 +41,23 @@ fn plus_one(a: usize) -> Option<usize> {
/// `$convert` is the underlying `encoding_rs::mem` function to use
/// `$other_ty` is the type of the input slice
/// `$math` is the worst-case length math that `$convert` expects
macro_rules! shrinking_conversion {
($name:ident,
$convert:ident,
$other_ty:ty,
$math:ident) => (
- fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<(), ()> {
- let written = {
- let needed = $math(other.len()).ok_or(())?;
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(old_len.checked_add(needed).ok_or(())?)?
- };
- $convert(other, &mut buffer[old_len..])
+ fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<BulkWriteOk, ()> {
+ let needed = $math(other.len()).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(old_len.checked_add(needed).ok_or(())?, old_len)?
};
- unsafe {
- // TODO: Shrink buffer
- self.fallible_set_length((old_len + written) as u32)?;
- }
- Ok(())
+ let written = $convert(other, &mut handle.as_mut_slice()[old_len..]);
+ Ok(handle.finish(old_len + written)) // XXX maybe shrink buffer
}
)
}
/// A conversion where the number of code units in the output is always equal
/// to the number of code units in the input.
///
/// Takes the name of the method to be generated, the name of the conversion
@@ -70,58 +65,51 @@ macro_rules! shrinking_conversion {
///
/// `$name` is the name of the function to generate
/// `$convert` is the underlying `encoding_rs::mem` function to use
/// `$other_ty` is the type of the input slice
macro_rules! constant_conversion {
($name:ident,
$convert:ident,
$other_ty:ty) => (
- fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<(), ()> {
- let needed = old_len.checked_add(other.len()).ok_or(())?;
- {
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(needed)?
- };
- $convert(other, &mut buffer[old_len..])
- }
- unsafe {
- // Truncation to u32 is OK, because `fallible_maybe_expand_capacity()`
- // would have failed if not.
- self.fallible_set_length(needed as u32)?;
- }
- Ok(())
+ fn $name(&mut self, other: $other_ty, old_len: usize) -> Result<BulkWriteOk, ()> {
+ let new_len = old_len.checked_add(other.len()).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(new_len, old_len)?
+ };
+ $convert(other, &mut handle.as_mut_slice()[old_len..]);
+ Ok(handle.finish(new_len))
}
)
}
/// An intermediate check for avoiding a copy and having an `nsStringBuffer`
/// refcount increment instead when both `self` and `other` are `nsACString`s,
/// `other` is entirely ASCII and all old data in `self` is discarded.
///
/// `$name` is the name of the function to generate
/// `$impl` is the underlying conversion that takes a slice and that is used
/// when we can't just adopt the incoming buffer as-is
/// `$string_like` is the kind of input taken
macro_rules! ascii_copy_avoidance {
($name:ident,
$impl:ident,
$string_like:ident) => (
- fn $name<T: $string_like + ?Sized>(&mut self, other: &T, old_len: usize) -> Result<(), ()> {
+ fn $name<T: $string_like + ?Sized>(&mut self, other: &T, old_len: usize) -> Result<BulkWriteOk, ()> {
let adapter = other.adapt();
let other_slice = adapter.as_ref();
let num_ascii = if adapter.is_abstract() && old_len == 0 {
let up_to = Encoding::ascii_valid_up_to(other_slice);
if up_to == other_slice.len() {
// Calling something whose argument can be obtained from
// the adapter rather than an nsStringLike avoids a huge
// lifetime mess by keeping nsStringLike and
// Latin1StringLike free of lifetime interdependencies.
if unsafe { Gecko_FallibleAssignCString(self, other.adapt().as_ptr()) } {
- return Ok(());
+ return Ok(BulkWriteOk{});
} else {
return Err(());
}
}
Some(up_to)
} else {
None
};
@@ -147,32 +135,32 @@ impl nsAString {
pub fn assign_str(&mut self, other: &str) {
self.fallible_append_str_impl(other, 0)
.expect("Out of memory");
}
/// Convert a valid UTF-8 string into valid UTF-16 and fallibly replace the
/// content of this string with the conversion result.
pub fn fallible_assign_str(&mut self, other: &str) -> Result<(), ()> {
- self.fallible_append_str_impl(other, 0)
+ self.fallible_append_str_impl(other, 0).map(|_| ())
}
/// Convert a valid UTF-8 string into valid UTF-16 and append the conversion
/// to this string.
pub fn append_str(&mut self, other: &str) {
let len = self.len();
self.fallible_append_str_impl(other, len)
.expect("Out of memory");
}
/// Convert a valid UTF-8 string into valid UTF-16 and fallibly append the
/// conversion to this string.
pub fn fallible_append_str(&mut self, other: &str) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_str_impl(other, len)
+ self.fallible_append_str_impl(other, len).map(|_| ())
}
// Potentially-invalid UTF-8 to UTF-16
// Documentation says the destination buffer needs to have
// one more code unit than the input.
shrinking_conversion!(
fallible_append_utf8_impl,
@@ -188,34 +176,34 @@ impl nsAString {
self.fallible_append_utf8_impl(other, 0)
.expect("Out of memory");
}
/// Convert a potentially-invalid UTF-8 string into valid UTF-16
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// fallibly replace the content of this string with the conversion result.
pub fn fallible_assign_utf8(&mut self, other: &[u8]) -> Result<(), ()> {
- self.fallible_append_utf8_impl(other, 0)
+ self.fallible_append_utf8_impl(other, 0).map(|_| ())
}
/// Convert a potentially-invalid UTF-8 string into valid UTF-16
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// append the conversion result to this string.
pub fn append_utf8(&mut self, other: &[u8]) {
let len = self.len();
self.fallible_append_utf8_impl(other, len)
.expect("Out of memory");
}
/// Convert a potentially-invalid UTF-8 string into valid UTF-16
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// fallibly append the conversion result to this string.
pub fn fallible_append_utf8(&mut self, other: &[u8]) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_utf8_impl(other, len)
+ self.fallible_append_utf8_impl(other, len).map(|_| ())
}
// Latin1 to UTF-16
constant_conversion!(fallible_append_latin1_impl, convert_latin1_to_utf16, &[u8]);
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-16 and replace the content of this string with the conversion result.
@@ -223,119 +211,110 @@ impl nsAString {
self.fallible_append_latin1_impl(other, 0)
.expect("Out of memory");
}
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-16 and fallibly replace the content of this string with the
/// conversion result.
pub fn fallible_assign_latin1(&mut self, other: &[u8]) -> Result<(), ()> {
- self.fallible_append_latin1_impl(other, 0)
+ self.fallible_append_latin1_impl(other, 0).map(|_| ())
}
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-16 and append the conversion result to this string.
pub fn append_latin1(&mut self, other: &[u8]) {
let len = self.len();
self.fallible_append_latin1_impl(other, len)
.expect("Out of memory");
}
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-16 and fallibly append the conversion result to this string.
pub fn fallible_append_latin1(&mut self, other: &[u8]) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_latin1_impl(other, len)
+ self.fallible_append_latin1_impl(other, len).map(|_| ())
}
}
impl nsACString {
// UTF-16 to UTF-8
fn fallible_append_utf16_to_utf8_impl(
&mut self,
other: &[u16],
old_len: usize,
- ) -> Result<(), ()> {
+ ) -> Result<BulkWriteOk, ()> {
// We first size the buffer for ASCII if the first code unit is ASCII. If that turns out not to
// be enough, we size for the worst case given the length of the remaining input at that point.
- // Lexical lifetimes make this a bit messy.
- let mut written = 0;
- {
- if let Some(first) = other.first() {
- let (needed, filled, num_ascii) = if *first < 0x80 {
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(old_len
- .checked_add(other.len())
- .ok_or(())?)?
- };
- let num_ascii = copy_basic_latin_to_ascii(other, &mut buffer[old_len..]);
- let filled = old_len + num_ascii;
- let available = buffer.len() - filled;
- let left = other.len() - num_ascii;
- let needed = times_three_plus_one(left).ok_or(())?;
- if needed <= available {
- written = num_ascii +
- convert_utf16_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
- (0, 0, 0)
- } else {
- (needed, filled, num_ascii)
- }
- } else {
- let needed = times_three_plus_one(other.len()).ok_or(())?;
- (needed, old_len, 0)
+ let (filled, num_ascii, mut handle) = if let Some(first) = other.first() {
+ if *first < 0x80 {
+ let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(new_len_with_ascii, old_len)?
};
- if needed != 0 {
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(filled.checked_add(needed).ok_or(())?)?
- };
- written = num_ascii +
- convert_utf16_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
+ let num_ascii = copy_basic_latin_to_ascii(other, &mut handle.as_mut_slice()[old_len..]);
+ let left = other.len() - num_ascii;
+ if left == 0 {
+ return Ok(handle.finish(old_len + num_ascii));
}
+ let filled = old_len + num_ascii;
+ let needed = times_three_plus_one(left).ok_or(())?;
+ let new_len = filled.checked_add(needed).ok_or(())?;
+ unsafe {
+ handle.restart_bulk_write(new_len, filled)?;
+ }
+ (filled, num_ascii, handle)
} else {
- return Ok(());
+ // Started with non-ASCII. Compute worst case
+ let needed = times_three_plus_one(other.len()).ok_or(())?;
+ let new_len = old_len.checked_add(needed).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(new_len, old_len)?
+ };
+ (old_len, 0, handle)
}
- }
- unsafe {
- // TODO: Shrink buffer
- self.fallible_set_length((old_len + written) as u32)?;
- }
- Ok(())
+ } else {
+ // Other was zero-length slice
+ return Ok(BulkWriteOk{});
+ };
+ let written = convert_utf16_to_utf8(&other[num_ascii..], &mut handle.as_mut_slice()[filled..]);
+ Ok(handle.finish(filled + written))
}
/// Convert a potentially-invalid UTF-16 string into valid UTF-8
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// replace the content of this string with the conversion result.
pub fn assign_utf16_to_utf8(&mut self, other: &[u16]) {
self.fallible_append_utf16_to_utf8_impl(other, 0)
.expect("Out of memory");
}
/// Convert a potentially-invalid UTF-16 string into valid UTF-8
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// fallibly replace the content of this string with the conversion result.
pub fn fallible_assign_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> {
- self.fallible_append_utf16_to_utf8_impl(other, 0)
+ self.fallible_append_utf16_to_utf8_impl(other, 0).map(|_| ())
}
/// Convert a potentially-invalid UTF-16 string into valid UTF-8
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// append the conversion result to this string.
pub fn append_utf16_to_utf8(&mut self, other: &[u16]) {
let len = self.len();
self.fallible_append_utf16_to_utf8_impl(other, len)
.expect("Out of memory");
}
/// Convert a potentially-invalid UTF-16 string into valid UTF-8
/// (replacing invalid sequences with the REPLACEMENT CHARACTER) and
/// fallibly append the conversion result to this string.
pub fn fallible_append_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_utf16_to_utf8_impl(other, len)
+ self.fallible_append_utf16_to_utf8_impl(other, len).map(|_| ())
}
// UTF-16 to Latin1
constant_conversion!(
fallible_append_utf16_to_latin1_lossy_impl,
convert_utf16_to_latin1_lossy,
&[u16]
@@ -362,17 +341,17 @@ impl nsACString {
///
/// # Panics
///
/// If the input contains code points above U+00FF or is not valid UTF-16,
/// panics in debug mode and produces garbage in a memory-safe way in
/// release builds. The nature of the garbage may differ based on CPU
/// architecture and must not be relied upon.
pub fn fallible_assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> {
- self.fallible_append_utf16_to_latin1_lossy_impl(other, 0)
+ self.fallible_append_utf16_to_latin1_lossy_impl(other, 0).map(|_| ())
}
/// Convert a UTF-16 string whose all code points are below U+0100 into
/// a Latin1 (scalar value is byte value; not windows-1252!) string and
/// append the conversion result to this string.
///
/// # Panics
///
@@ -393,50 +372,48 @@ impl nsACString {
/// # Panics
///
/// If the input contains code points above U+00FF or is not valid UTF-16,
/// panics in debug mode and produces garbage in a memory-safe way in
/// release builds. The nature of the garbage may differ based on CPU
/// architecture and must not be relied upon.
pub fn fallible_append_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_utf16_to_latin1_lossy_impl(other, len)
+ self.fallible_append_utf16_to_latin1_lossy_impl(other, len).map(|_| ())
}
// UTF-8 to Latin1
ascii_copy_avoidance!(
fallible_append_utf8_to_latin1_lossy_check,
fallible_append_utf8_to_latin1_lossy_impl,
nsCStringLike
);
fn fallible_append_utf8_to_latin1_lossy_impl(
&mut self,
other: &[u8],
old_len: usize,
maybe_num_ascii: Option<usize>,
- ) -> Result<(), ()> {
+ ) -> Result<BulkWriteOk, ()> {
+ let new_len = old_len.checked_add(other.len()).ok_or(())?;
let num_ascii = maybe_num_ascii.unwrap_or(0);
- // This may overflow, but if overflow happens here, an overflow also happens where checked.
+ // Already checked for overflow above, so this can't overflow.
let old_len_plus_num_ascii = old_len + num_ascii;
+ let mut handle = unsafe {
+ self.bulk_write(new_len, old_len)?
+ };
let written = {
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(old_len.checked_add(other.len()).ok_or(())?)?
- };
+ let buffer = handle.as_mut_slice();
if num_ascii != 0 {
(&mut buffer[old_len..old_len_plus_num_ascii]).copy_from_slice(&other[..num_ascii]);
}
convert_utf8_to_latin1_lossy(&other[num_ascii..], &mut buffer[old_len_plus_num_ascii..])
};
- unsafe {
- // TODO: Shrink buffer
- self.fallible_set_length((old_len_plus_num_ascii + written) as u32)?;
- }
- Ok(())
+ Ok(handle.finish(old_len_plus_num_ascii + written)) // TODO: Shrink buffer
}
/// Convert a UTF-8 string whose all code points are below U+0100 into
/// a Latin1 (scalar value is byte value; not windows-1252!) string and
/// replace the content of this string with the conversion result.
///
/// # Panics
///
@@ -458,17 +435,17 @@ impl nsACString {
/// If the input contains code points above U+00FF or is not valid UTF-8,
/// panics in debug mode and produces garbage in a memory-safe way in
/// release builds. The nature of the garbage may differ based on CPU
/// architecture and must not be relied upon.
pub fn fallible_assign_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(
&mut self,
other: &T,
) -> Result<(), ()> {
- self.fallible_append_utf8_to_latin1_lossy_check(other, 0)
+ self.fallible_append_utf8_to_latin1_lossy_check(other, 0).map(|_| ())
}
/// Convert a UTF-8 string whose all code points are below U+0100 into
/// a Latin1 (scalar value is byte value; not windows-1252!) string and
/// append the conversion result to this string.
///
/// # Panics
///
@@ -492,94 +469,100 @@ impl nsACString {
/// panics in debug mode and produces garbage in a memory-safe way in
/// release builds. The nature of the garbage may differ based on CPU
/// architecture and must not be relied upon.
pub fn fallible_append_utf8_to_latin1_lossy<T: nsCStringLike + ?Sized>(
&mut self,
other: &T,
) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_utf8_to_latin1_lossy_check(other, len)
+ self.fallible_append_utf8_to_latin1_lossy_check(other, len).map(|_| ())
}
// Latin1 to UTF-8 CString
ascii_copy_avoidance!(
fallible_append_latin1_to_utf8_check,
fallible_append_latin1_to_utf8_impl,
Latin1StringLike
);
fn fallible_append_latin1_to_utf8_impl(
&mut self,
other: &[u8],
old_len: usize,
maybe_num_ascii: Option<usize>,
- ) -> Result<(), ()> {
- // We first size the buffer for ASCII. If that turns out not to be enough, we size for the worst
- // case given the length of the remaining input at that point. Lexical lifetimes make this a bit
- // messy.
- let mut written = 0;
- {
- let (needed, filled, num_ascii) = {
- if let Some(num_ascii) = maybe_num_ascii {
- let filled = old_len + num_ascii;
- let left = other.len() - num_ascii;
- let needed = left.checked_mul(2).ok_or(())?;
- (needed, filled, num_ascii)
- } else {
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(old_len
- .checked_add(other.len())
- .ok_or(())?)?
- };
- let num_ascii = copy_ascii_to_ascii(other, &mut buffer[old_len..]);
- let filled = old_len + num_ascii;
- let available = buffer.len() - filled;
- let left = other.len() - num_ascii;
- let needed = left.checked_mul(2).ok_or(())?;
- if needed <= available {
- written = num_ascii +
- convert_latin1_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
- (0, 0, 0)
- } else {
- (needed, filled, num_ascii)
- }
+ ) -> Result<BulkWriteOk, ()> {
+ let (filled, num_ascii, mut handle) = if let Some(num_ascii) = maybe_num_ascii {
+ // Wrapper checked for ASCII
+ let left = other.len() - num_ascii;
+ let filled = old_len + num_ascii;
+ let needed = left.checked_mul(2).ok_or(())?;
+ let new_len = filled.checked_add(needed).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(new_len, old_len)?
+ };
+ if num_ascii != 0 {
+ (&mut handle.as_mut_slice()[old_len..filled]).copy_from_slice(&other[..num_ascii]);
+ }
+ (filled, num_ascii, handle)
+ } else if let Some(first) = other.first() {
+ // Wrapper didn't check for ASCII, so let's see if `other` starts with ASCII
+ if *first < 0x80 {
+ // `other` starts with ASCII, so let's first size the buffer
+ // with optimism that it's ASCII-only.
+ let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(new_len_with_ascii, old_len)?
+ };
+ let num_ascii = copy_ascii_to_ascii(other, &mut handle.as_mut_slice()[old_len..]);
+ let left = other.len() - num_ascii;
+ let filled = old_len + num_ascii;
+ if left == 0 {
+ // `other` was all ASCII
+ return Ok(handle.finish(filled));
}
- };
- if needed != 0 {
- let buffer = unsafe {
- self.fallible_maybe_expand_capacity(filled.checked_add(needed).ok_or(())?)?
+ let needed = left.checked_mul(2).ok_or(())?;
+ let new_len = filled.checked_add(needed).ok_or(())?;
+ unsafe {
+ handle.restart_bulk_write(new_len, filled)?;
+ }
+ (filled, num_ascii, handle)
+ } else {
+ // Started with non-ASCII. Assume worst case.
+ let needed = other.len().checked_mul(2).ok_or(())?;
+ let new_len = old_len.checked_add(needed).ok_or(())?;
+ let mut handle = unsafe {
+ self.bulk_write(new_len, old_len)?
};
- written =
- num_ascii + convert_latin1_to_utf8(&other[num_ascii..], &mut buffer[filled..]);
+ (old_len, 0, handle)
}
- }
- unsafe {
- // TODO: Shrink buffer
- self.fallible_set_length((old_len + written) as u32)?;
- }
- Ok(())
+ } else {
+ // Other was zero-length slice
+ return Ok(BulkWriteOk{});
+ };
+ let written = convert_latin1_to_utf8(&other[num_ascii..], &mut handle.as_mut_slice()[filled..]);
+ Ok(handle.finish(filled + written))
}
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-8 and replace the content of this string with the conversion result.
pub fn assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) {
self.fallible_append_latin1_to_utf8_check(other, 0)
.expect("Out of memory");
}
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-8 and fallibly replace the content of this string with the
/// conversion result.
pub fn fallible_assign_latin1_to_utf8<T: Latin1StringLike + ?Sized>(
&mut self,
other: &T,
) -> Result<(), ()> {
- self.fallible_append_latin1_to_utf8_check(other, 0)
+ self.fallible_append_latin1_to_utf8_check(other, 0).map(|_| ())
}
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-8 and append the conversion result to this string.
pub fn append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(&mut self, other: &T) {
let len = self.len();
self.fallible_append_latin1_to_utf8_check(other, len)
.expect("Out of memory");
@@ -587,17 +570,17 @@ impl nsACString {
/// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!)
/// into UTF-8 and fallibly append the conversion result to this string.
pub fn fallible_append_latin1_to_utf8<T: Latin1StringLike + ?Sized>(
&mut self,
other: &T,
) -> Result<(), ()> {
let len = self.len();
- self.fallible_append_latin1_to_utf8_check(other, len)
+ self.fallible_append_latin1_to_utf8_check(other, len).map(|_| ())
}
}
#[no_mangle]
pub unsafe extern "C" fn nsstring_fallible_append_utf8_impl(
this: *mut nsAString,
other: *const u8,
other_len: usize,
--- a/servo/support/gecko/nsstring/src/lib.rs
+++ b/servo/support/gecko/nsstring/src/lib.rs
@@ -132,16 +132,23 @@ mod conversions;
pub use self::conversions::nsstring_fallible_append_utf8_impl;
pub use self::conversions::nsstring_fallible_append_latin1_impl;
pub use self::conversions::nscstring_fallible_append_utf16_to_utf8_impl;
pub use self::conversions::nscstring_fallible_append_utf16_to_latin1_lossy_impl;
pub use self::conversions::nscstring_fallible_append_utf8_to_latin1_lossy_check;
pub use self::conversions::nscstring_fallible_append_latin1_to_utf8_check;
+/// A type for showing that `finish()` was called on a `BulkWriteHandle`.
+/// Instantiating this type from elsewhere is basically an assertion that
+/// there is no `BulkWriteHandle` around, so be very careful with instantiating
+/// this type!
+pub struct BulkWriteOk {
+}
+
///////////////////////////////////
// Internal Implementation Flags //
///////////////////////////////////
mod data_flags {
bitflags! {
// While this has the same layout as u16, it cannot be passed
// over FFI safely as a u16.
@@ -247,61 +254,122 @@ macro_rules! string_like {
impl $StringLike for Box<[$char_t]> {
fn adapt(&self) -> $StringAdapter {
$StringAdapter::Borrowed($Str::from(&self[..]))
}
}
}
}
+impl<'a> Drop for nsAStringBulkWriteHandle<'a> {
+ /// This only runs in error cases. In success cases, `finish()`
+ /// calles `forget(self)`.
+ fn drop(&mut self) {
+ assert_ne!(self.capacity, 0);
+ // Zero-length strings are special, so we can't make one
+ // that doesn't follow the zero-length rules.
+ // The old zero terminator may be gone by now, so we need
+ // to write a new one somewhere and make length match.
+ // We can use a length between 1 and self.capacity.
+ // Seems prudent to overwrite the uninitialized memory.
+ // Using the length 1 leaves the shortest memory to overwrite.
+ // U+FFFD is the safest placeholder.
+ unsafe {
+ // The borrow checker doesn't allow a reference to reference
+ // transmute, so let's use pointers.
+ let string_ptr = self.string as *mut nsAString;
+ let this: *mut nsStringRepr = mem::transmute(string_ptr);
+ (*this).length = 1u32;
+ *(*this).data = 0xFFFDu16;
+ *((*this).data.offset(1isize)) = 0;
+ }
+ }
+}
+
+impl<'a> Drop for nsACStringBulkWriteHandle<'a> {
+ /// This only runs in error cases. In success cases, `finish()`
+ /// calles `forget(self)`.
+ fn drop(&mut self) {
+ assert_ne!(self.capacity, 0);
+ // Zero-length strings are special, so we can't make one
+ // that doesn't follow the zero-length rules.
+ // The old zero terminator may be gone by now, so we need
+ // to write a new one somewhere and make length match.
+ // We can use a length between 1 and self.capacity.
+ // Seems prudent to overwrite the uninitialized memory.
+ // Using the length 1 leaves the shortest memory to overwrite.
+ // U+FFFD is the safest placeholder, but when it doesn't fit,
+ // let's use ASCII substitute.
+ unsafe {
+ // The borrow checker doesn't allow a reference to reference
+ // transmute, so let's use pointers.
+ let string_ptr = self.string as *mut nsACString;
+ let this: *mut nsCStringRepr = mem::transmute(string_ptr);
+ if self.capacity >= 3 {
+ (*this).length = 3u32;
+ *(*this).data = 0xEFu8; // U+FFFD doesn't fit
+ *((*this).data.offset(1isize)) = 0xBFu8;
+ *((*this).data.offset(2isize)) = 0xBDu8;
+ *((*this).data.offset(3isize)) = 0;
+ } else {
+ (*this).length = 1u32;
+ *(*this).data = 0x1Au8; // U+FFFD doesn't fit
+ *((*this).data.offset(1isize)) = 0;
+ }
+ }
+ }
+}
+
macro_rules! define_string_types {
{
char_t = $char_t: ty;
AString = $AString: ident;
String = $String: ident;
Str = $Str: ident;
StringLike = $StringLike: ident;
StringAdapter = $StringAdapter: ident;
StringRepr = $StringRepr: ident;
+ BulkWriteHandle = $BulkWriteHandle: ident;
+
drop = $drop: ident;
assign = $assign: ident, $fallible_assign: ident;
take_from = $take_from: ident, $fallible_take_from: ident;
append = $append: ident, $fallible_append: ident;
set_length = $set_length: ident, $fallible_set_length: ident;
begin_writing = $begin_writing: ident, $fallible_begin_writing: ident;
- fallible_maybe_expand_capacity = $fallible_maybe_expand_capacity: ident;
+ start_bulk_write = $start_bulk_write: ident;
} => {
/// The representation of a ns[C]String type in C++. This type is
/// used internally by our definition of ns[C]String to ensure layout
/// compatibility with the C++ ns[C]String type.
///
/// This type may also be used in place of a C++ ns[C]String inside of
/// struct definitions which are shared with C++, as it has identical
/// layout to our ns[C]String type.
///
/// This struct will leak its data if dropped from rust. See the module
/// documentation for more information on this type.
#[repr(C)]
#[derive(Debug)]
pub struct $StringRepr {
- data: *const $char_t,
+ data: *mut $char_t,
length: u32,
dataflags: DataFlags,
classflags: ClassFlags,
}
impl $StringRepr {
fn new(classflags: ClassFlags) -> $StringRepr {
static NUL: $char_t = 0;
$StringRepr {
- data: &NUL,
+ data: unsafe { mem::transmute(&NUL as *const $char_t) },
length: 0,
dataflags: DataFlags::TERMINATED | DataFlags::LITERAL,
classflags: classflags,
}
}
}
impl Deref for $StringRepr {
@@ -316,16 +384,66 @@ macro_rules! define_string_types {
impl DerefMut for $StringRepr {
fn deref_mut(&mut self) -> &mut $AString {
unsafe {
mem::transmute(self)
}
}
}
+ pub struct $BulkWriteHandle<'a> {
+ string: &'a mut $AString,
+ capacity: usize,
+ }
+
+ impl<'a> $BulkWriteHandle<'a> {
+ fn new(string: &'a mut $AString, capacity: usize) -> Self {
+ $BulkWriteHandle{ string: string, capacity: capacity }
+ }
+
+ pub unsafe fn restart_bulk_write(&mut self, capacity: usize, units_to_preserve: usize) -> Result<(), ()> {
+ self.capacity = self.string.start_bulk_write_impl(capacity, units_to_preserve)?;
+ Ok(())
+ }
+
+ pub fn finish(self, length: usize) -> BulkWriteOk {
+ // NOTE: Drop is implemented outside the macro earlier in this file
+ assert!(length <= self.capacity);
+ if self.capacity == 0 {
+ mem::forget(self); // Don't run the failure path in drop()
+ return BulkWriteOk{};
+ }
+ unsafe {
+ // The borrow checker doesn't allow a reference to reference
+ // transmute, so let's use pointers.
+ let string_ptr = self.string as *mut $AString;
+ let this: *mut $StringRepr = mem::transmute(string_ptr);
+ (*this).length = length as u32;
+ *((*this).data.offset(length as isize)) = 0;
+ }
+ mem::forget(self); // Don't run the failure path in drop()
+ BulkWriteOk{}
+ }
+
+ pub fn as_mut_slice(&mut self) -> &mut [$char_t] {
+ unsafe {
+ let ptr = if self.capacity == 0 {
+ ::std::ptr::NonNull::<$char_t>::dangling().as_ptr()
+ } else {
+ // The borrow checker doesn't allow a reference to reference
+ // transmute, so let's use pointers.
+ let string_ptr = self.string as *mut $AString;
+ let this: *mut $StringRepr = mem::transmute(string_ptr);
+ (*this).data
+ };
+ ::std::slice::from_raw_parts_mut(ptr, self.capacity)
+ }
+ }
+ }
+
/// This type is the abstract type which is used for interacting with
/// strings in rust. Each string type can derefence to an instance of
/// this type, which provides the useful operations on strings.
///
/// NOTE: Rust thinks this type has a size of 0, because the data
/// associated with it is not necessarially safe to move. It is not safe
/// to construct a nsAString yourself, unless it is received by
/// dereferencing one of these types.
@@ -456,41 +574,46 @@ macro_rules! define_string_types {
Err(())
} else {
Ok(slice::from_raw_parts_mut(ptr, len))
}
}
}
}
+ pub unsafe fn bulk_write(&mut self, capacity: usize, units_to_preserve: usize) -> Result<$BulkWriteHandle, ()> {
+ let capacity = self.start_bulk_write_impl(capacity, units_to_preserve)?;
+ Ok($BulkWriteHandle::new(self, capacity))
+ }
+
/// Unshares the buffer of the string, sets the capacity to the
/// allocation size resulting from rounding up `len`. Set the
/// length of the string to the rounded-up capacity and returns
/// the buffer as a mutable slice.
///
/// Fails also if the new length doesn't fit in 32 bits.
///
/// # Safety
///
/// Unsafe because of exposure of uninitialized memory.
- unsafe fn fallible_maybe_expand_capacity(&mut self, len: usize) -> Result<&mut [$char_t], ()> {
- if len == 0 {
+ unsafe fn start_bulk_write_impl(&mut self, capacity: usize, units_to_preserve: usize) -> Result<usize, ()> {
+ debug_assert!(units_to_preserve <= capacity);
+ if capacity == 0 {
self.fallible_set_length(0)?;
- // Use an arbitrary non-null value as the pointer
- Ok(slice::from_raw_parts_mut(0x1 as *mut $char_t, 0))
- } else if len > u32::max_value() as usize {
+ Ok(0)
+ } else if capacity > u32::max_value() as usize {
Err(())
} else {
- let mut len32 = len as u32;
- let ptr = $fallible_maybe_expand_capacity(self, &mut len32);
- if ptr.is_null() {
- Err(())
- } else {
- Ok(slice::from_raw_parts_mut(ptr, len32 as usize))
+ let capacity32 = capacity as u32;
+ let rounded = $start_bulk_write(self, capacity32, units_to_preserve as u32);
+ if rounded == u32::max_value() {
+ return Err(())
}
+ debug_assert_ne!(rounded, 0);
+ Ok(rounded as usize)
}
}
}
impl Deref for $AString {
type Target = [$char_t];
fn deref(&self) -> &[$char_t] {
unsafe {
@@ -585,17 +708,17 @@ macro_rules! define_string_types {
impl<'a> From<&'a [$char_t]> for $Str<'a> {
fn from(s: &'a [$char_t]) -> $Str<'a> {
assert!(s.len() < (u32::MAX as usize));
if s.is_empty() {
return $Str::new();
}
$Str {
hdr: $StringRepr {
- data: s.as_ptr(),
+ data: unsafe { ::mem::transmute(s.as_ptr()) },
length: s.len() as u32,
dataflags: DataFlags::empty(),
classflags: ClassFlags::empty(),
},
_marker: PhantomData,
}
}
}
@@ -742,17 +865,17 @@ macro_rules! define_string_types {
let length = s.len() as u32;
s.push(0); // null terminator
// SAFETY NOTE: This method produces an data_flags::OWNED
// ns[C]String from a Box<[$char_t]>. this is only safe
// because in the Gecko tree, we use the same allocator for
// Rust code as for C++ code, meaning that our box can be
// legally freed with libc::free().
- let ptr = s.as_ptr();
+ let ptr = s.as_mut_ptr();
mem::forget(s);
unsafe {
Gecko_IncrementStringAdoptCount(ptr as *mut _);
}
$String {
hdr: $StringRepr {
data: ptr,
length: length,
@@ -870,23 +993,25 @@ define_string_types! {
String = nsCString;
Str = nsCStr;
StringLike = nsCStringLike;
StringAdapter = nsCStringAdapter;
StringRepr = nsCStringRepr;
+ BulkWriteHandle = nsACStringBulkWriteHandle;
+
drop = Gecko_FinalizeCString;
assign = Gecko_AssignCString, Gecko_FallibleAssignCString;
take_from = Gecko_TakeFromCString, Gecko_FallibleTakeFromCString;
append = Gecko_AppendCString, Gecko_FallibleAppendCString;
set_length = Gecko_SetLengthCString, Gecko_FallibleSetLengthCString;
begin_writing = Gecko_BeginWritingCString, Gecko_FallibleBeginWritingCString;
- fallible_maybe_expand_capacity = Gecko_FallibleMaybeExpandCapacityCString;
+ start_bulk_write = Gecko_StartBulkWriteCString;
}
impl nsACString {
pub unsafe fn as_str_unchecked(&self) -> &str {
str::from_utf8_unchecked(self)
}
}
@@ -998,23 +1123,25 @@ define_string_types! {
String = nsString;
Str = nsStr;
StringLike = nsStringLike;
StringAdapter = nsStringAdapter;
StringRepr = nsStringRepr;
+ BulkWriteHandle = nsAStringBulkWriteHandle;
+
drop = Gecko_FinalizeString;
assign = Gecko_AssignString, Gecko_FallibleAssignString;
take_from = Gecko_TakeFromString, Gecko_FallibleTakeFromString;
append = Gecko_AppendString, Gecko_FallibleAppendString;
set_length = Gecko_SetLengthString, Gecko_FallibleSetLengthString;
begin_writing = Gecko_BeginWritingString, Gecko_FallibleBeginWritingString;
- fallible_maybe_expand_capacity = Gecko_FallibleMaybeExpandCapacityString;
+ start_bulk_write = Gecko_StartBulkWriteString;
}
// NOTE: The From impl for a string slice for nsString produces a <'static>
// lifetime, as it allocates.
impl<'a> From<&'a str> for nsString {
fn from(s: &'a str) -> nsString {
s.encode_utf16().collect::<Vec<u16>>().into()
}
@@ -1070,31 +1197,31 @@ extern "C" {
fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString);
fn Gecko_SetLengthCString(this: *mut nsACString, length: u32);
fn Gecko_BeginWritingCString(this: *mut nsACString) -> *mut u8;
fn Gecko_FallibleAssignCString(this: *mut nsACString, other: *const nsACString) -> bool;
fn Gecko_FallibleTakeFromCString(this: *mut nsACString, other: *mut nsACString) -> bool;
fn Gecko_FallibleAppendCString(this: *mut nsACString, other: *const nsACString) -> bool;
fn Gecko_FallibleSetLengthCString(this: *mut nsACString, length: u32) -> bool;
fn Gecko_FallibleBeginWritingCString(this: *mut nsACString) -> *mut u8;
- fn Gecko_FallibleMaybeExpandCapacityCString(this: *mut nsACString, length: *mut u32) -> *mut u8;
+ fn Gecko_StartBulkWriteCString(this: *mut nsACString, capacity: u32, units_to_preserve: u32) -> u32;
fn Gecko_FinalizeString(this: *mut nsAString);
fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString);
fn Gecko_TakeFromString(this: *mut nsAString, other: *mut nsAString);
fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString);
fn Gecko_SetLengthString(this: *mut nsAString, length: u32);
fn Gecko_BeginWritingString(this: *mut nsAString) -> *mut u16;
fn Gecko_FallibleAssignString(this: *mut nsAString, other: *const nsAString) -> bool;
fn Gecko_FallibleTakeFromString(this: *mut nsAString, other: *mut nsAString) -> bool;
fn Gecko_FallibleAppendString(this: *mut nsAString, other: *const nsAString) -> bool;
fn Gecko_FallibleSetLengthString(this: *mut nsAString, length: u32) -> bool;
fn Gecko_FallibleBeginWritingString(this: *mut nsAString) -> *mut u16;
- fn Gecko_FallibleMaybeExpandCapacityString(this: *mut nsAString, length: *mut u32) -> *mut u16;
+ fn Gecko_StartBulkWriteString(this: *mut nsAString, capacity: u32, units_to_preserve: u32) -> u32;
}
//////////////////////////////////////
// Repr Validation Helper Functions //
//////////////////////////////////////
pub mod test_helpers {
//! This module only exists to help with ensuring that the layout of the
--- a/xpcom/string/nsSubstring.cpp
+++ b/xpcom/string/nsSubstring.cpp
@@ -450,19 +450,19 @@ char* Gecko_BeginWritingCString(nsACStri
return aThis->BeginWriting();
}
char* Gecko_FallibleBeginWritingCString(nsACString* aThis)
{
return aThis->BeginWriting(mozilla::fallible);
}
-char* Gecko_FallibleMaybeExpandCapacityCString(nsACString* aThis, uint32_t* aCapacity)
+uint32_t Gecko_StartBulkWriteCString(nsACString* aThis, uint32_t aCapacity, uint32_t aUnitsToPreserve)
{
- return aThis->MaybeExpandCapacity(aCapacity, mozilla::fallible);
+ return aThis->StartBulkWrite(aCapacity, aUnitsToPreserve);
}
void Gecko_FinalizeString(nsAString* aThis)
{
aThis->~nsAString();
}
void Gecko_AssignString(nsAString* aThis, const nsAString* aOther)
@@ -510,14 +510,14 @@ char16_t* Gecko_BeginWritingString(nsASt
return aThis->BeginWriting();
}
char16_t* Gecko_FallibleBeginWritingString(nsAString* aThis)
{
return aThis->BeginWriting(mozilla::fallible);
}
-char16_t* Gecko_FallibleMaybeExpandCapacityString(nsAString* aThis, uint32_t* aCapacity)
+uint32_t Gecko_StartBulkWriteString(nsAString* aThis, uint32_t aCapacity, uint32_t aUnitsToPreserve)
{
- return aThis->MaybeExpandCapacity(aCapacity, mozilla::fallible);
+ return aThis->StartBulkWrite(aCapacity, aUnitsToPreserve);
}
} // extern "C"
--- a/xpcom/string/nsTSubstring.cpp
+++ b/xpcom/string/nsTSubstring.cpp
@@ -42,16 +42,116 @@ nsTSubstring<T>::nsTSubstring(char_type*
*/
template <typename T>
inline const nsTAutoString<T>*
AsAutoString(const nsTSubstring<T>* aStr)
{
return static_cast<const nsTAutoString<T>*>(aStr);
}
+template <typename T>
+uint32_t
+nsTSubstring<T>::StartBulkWrite(size_type aCapacity, size_type aUnitsToPreserve)
+{
+ // capacity does not include room for the terminating null char
+ size_type wantedCapacity = aCapacity;
+ size_type curCapacity = Capacity();
+
+ if (MOZ_UNLIKELY(!wantedCapacity)) {
+ this->mDataFlags &= ~DataFlags::VOIDED; // mutation clears voided flag
+ return 0;
+ }
+
+ // We've established that wantedCapacity > 0.
+ // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we
+ // need to allocate a new buffer. We cannot use the existing buffer even
+ // though it might be large enough.
+
+ if (wantedCapacity <= curCapacity) {
+ return curCapacity;
+ }
+
+ // If |wantedCapacity > kMaxCapacity|, then our doubling algorithm may not be
+ // able to allocate it. Just bail out in cases like that. We don't want
+ // to be allocating 2GB+ strings anyway.
+ static_assert((sizeof(nsStringBuffer) & 0x1) == 0,
+ "bad size for nsStringBuffer");
+ if (MOZ_UNLIKELY(!CheckCapacity(wantedCapacity))) {
+ return UINT32_MAX;
+ }
+
+ // We increase our capacity so that the allocated buffer grows
+ // exponentially, which gives us amortized O(1) appending. Below the
+ // threshold, we use powers-of-two. Above the threshold, we grow by at
+ // least 1.125, rounding up to the nearest MiB.
+ const size_type slowGrowthThreshold = 8 * 1024 * 1024;
+
+ // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and
+ // storageSize below wants extra 1 * sizeof(char_type).
+ const size_type neededExtraSpace =
+ sizeof(nsStringBuffer) / sizeof(char_type) + 1;
+
+ size_type temp;
+ if (wantedCapacity >= slowGrowthThreshold) {
+ size_type minNewCapacity = curCapacity + (curCapacity >> 3); // multiply by 1.125
+ temp = XPCOM_MAX(wantedCapacity, minNewCapacity) + neededExtraSpace;
+
+ // Round up to the next multiple of MiB, but ensure the expected
+ // capacity doesn't include the extra space required by nsStringBuffer
+ // and null-termination.
+ const size_t MiB = 1 << 20;
+ temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace;
+ } else {
+ // Round up to the next power of two.
+ temp =
+ mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace;
+ }
+
+ size_type newCapacity = XPCOM_MIN(temp, kMaxCapacity);
+ MOZ_ASSERT(newCapacity >= wantedCapacity,
+ "should have hit the early return at the top");
+
+ // If the old buffer is a read-only heap buffer and this is an nsTAutoStringN,
+ // it's possible that we can use the inline buffer. Otherwise, we allocate an
+ // nsStringBuffer. We explicitly don't realloc an old nsStringBuffer in order
+ // to control what data is copied.
+
+ char_type* newData;
+ DataFlags newDataFlags;
+ if ((this->mClassFlags & ClassFlags::INLINE) &&
+ (newCapacity <= AsAutoString(this)->mInlineCapacity)) {
+ newCapacity = AsAutoString(this)->mInlineCapacity;
+ newData = (char_type*)AsAutoString(this)->mStorage;
+ newDataFlags = DataFlags::TERMINATED | DataFlags::INLINE;
+ } else {
+ size_type storageSize = (newCapacity + 1) * sizeof(char_type);
+ nsStringBuffer* newHdr =
+ nsStringBuffer::Alloc(storageSize).take();
+ if (!newHdr) {
+ return UINT32_MAX; // we are still in a consistent state
+ }
+
+ newData = (char_type*)newHdr->Data();
+ newDataFlags = DataFlags::TERMINATED | DataFlags::REFCOUNTED;
+ }
+
+ char_type* oldData = this->mData;
+ DataFlags oldFlags = this->mDataFlags;
+
+ this->mData = newData;
+ this->mDataFlags = newDataFlags;
+
+ if (oldData) {
+ char_traits::copy(this->mData, oldData, aUnitsToPreserve);
+ ::ReleaseData(oldData, oldFlags);
+ }
+
+ return newCapacity;
+}
+
/**
* this function is called to prepare mData for writing. the given capacity
* indicates the required minimum storage size for mData, in sizeof(char_type)
* increments. this function returns true if the operation succeeds. it also
* returns the old data and old flags members if mData is newly allocated.
* the old data must be released by the caller.
*/
template <typename T>
--- a/xpcom/string/nsTSubstring.h
+++ b/xpcom/string/nsTSubstring.h
@@ -590,45 +590,16 @@ public:
}
/**
* buffer access
*/
/**
- * If *aCapacity is larger than the current capacity, allocates a
- * buffer whose length is at least *aCapacity.
- *
- * Sets *aCapacity and the string's length to the actual capacity.
- *
- * Returns a pointer to the start of the buffer or nullptr if
- * allocation failed.
- *
- * Note that unlike GetMutableData, this rounds the length up to the
- * capacity.
- */
- inline char_type* MaybeExpandCapacity(size_type* aCapacity, const fallible_t& aFallible)
- {
- // SetCapacity unshares a shared buffer even then resizing is not
- // needed.
- if (!SetCapacity(*aCapacity, aFallible)) {
- return nullptr;
- }
- size_type capacity = Capacity();
- // SetCapacity doesn't stretch the logical length for us.
- this->mLength = capacity;
- *aCapacity = capacity;
- char_type* ptr = base_string_type::mData;
- // SetCapacity zero-terminated at intermediate length, not capacity.
- ptr[capacity] = 0;
- return ptr;
- }
-
- /**
* Get a const pointer to the string's internal buffer. The caller
* MUST NOT modify the characters at the returned address.
*
* @returns The length of the buffer in characters.
*/
inline size_type GetData(const char_type** aData) const
{
*aData = base_string_type::mData;
@@ -923,16 +894,54 @@ protected:
/**
* this function releases mData and does not change the value of
* any of its member variables. in other words, this function acts
* like a destructor.
*/
void NS_FASTCALL Finalize();
+public:
+ /**
+ * Prepares mData to be mutated such that the capacity of the string
+ * (not counting the zero-terminator) is at least aCapacity.
+ * Returns the actual capacity, which may be larger than what was
+ * requested or UINT32_MAX on allocation failure.
+ *
+ * mLength is ignored by this method. If the buffer is reallocated,
+ * aUnitsToPreserve specifies how many code units to copy over to
+ * the new buffer. The old buffer is freed if applicable.
+ *
+ * Unless the return value is UINT32_MAX to signal failure, this
+ * method the string in an invalid state! The caller is reponsible
+ * for zero-terminating the string and setting a valid mLength
+ * by calling FinishBulkWrite(). This method sets the flag to claim
+ * that the string is zero-terminated before it actually is.
+ *
+ * Once this method has been called and before FinishBulkWrite()
+ * has been called, only calls to Data() or this method again
+ * are valid. Do not call any other methods between calling this
+ * method and FinishBulkWrite().
+ */
+ uint32_t NS_FASTCALL StartBulkWrite(size_type aCapacity, size_type aUnitsToPreserve);
+
+ /**
+ * Restores the string to a valid state after a call to BulkWritePrep()
+ * that returned a non-UINT32_MAX value. The argument to this method
+ * must be less than or equal to the non-UINT32_MAX value returned by
+ * the most recent BulkWritePrep() call.
+ */
+ inline void FinishBulkWrite(size_type aLength) {
+ MOZ_ASSERT(aLength != UINT32_MAX, "OOM magic value passed as length.");
+ base_string_type::mData[aLength] = 0;
+ base_string_type::mLength = aLength;
+ AssertValid();
+ }
+
+protected:
/**
* this function prepares mData to be mutated.
*
* @param aCapacity specifies the required capacity of mData
* @param aOldData returns null or the old value of mData
* @param aOldFlags returns 0 or the old value of mDataFlags
*
* if mData is already mutable and of sufficient capacity, then this