Index: runtime/lib/string.cc |
diff --git a/runtime/lib/string.cc b/runtime/lib/string.cc |
index 5b450f4a71adf581e2bf87f04e07698e365f575d..2dd69e54f47d279453292688d874bf02d85513e9 100644 |
--- a/runtime/lib/string.cc |
+++ b/runtime/lib/string.cc |
@@ -103,6 +103,155 @@ DEFINE_NATIVE_ENTRY(StringBase_substringUnchecked, 3) { |
} |
+ |
+// Return the bitwise-or of all characters in the slice from start to end. |
+static uint16_t CharacterLimit(const String& string, |
+ intptr_t start, intptr_t end) { |
+ ASSERT(string.IsTwoByteString() || string.IsExternalTwoByteString()); |
+ // Maybe do loop unrolling, and handle two uint16_t in a single uint32_t |
+ // operation. |
+ NoGCScope no_gc; |
+ uint16_t result = 0; |
+ if (string.IsTwoByteString()) { |
+ for (intptr_t i = start; i < end; i++) { |
+ result |= TwoByteString::CharAt(string, i); |
+ } |
+ } else { |
+ for (intptr_t i = start; i < end; i++) { |
+ result |= ExternalTwoByteString::CharAt(string, i); |
+ } |
+ } |
+ return result; |
+} |
+ |
+static const intptr_t kLengthSize = 11; |
+static const intptr_t kLengthMask = (1 << kLengthSize) - 1; |
+ |
+static bool CheckSlicesOneByte(const String& base, |
+ const Array& matches, |
+ const int len) { |
+ Instance& object = Instance::Handle(); |
+ // Check each slice for one-bytedness. |
+ for (intptr_t i = 0; i < len; i++) { |
+ object ^= matches.At(i); |
+ if (object.IsSmi()) { |
+ intptr_t slice_start = Smi::Cast(object).Value(); |
+ intptr_t slice_end; |
+ if (slice_start < 0) { |
+ intptr_t bits = -slice_start; |
+ slice_start = bits >> kLengthSize; |
+ slice_end = slice_start + (bits & kLengthMask); |
+ } else { |
+ i++; |
+ if (i >= len) { |
+ // Bad format, handled later. |
+ return false; |
+ } |
+ object ^= matches.At(i); |
+ if (!object.IsSmi()) { |
+ // Bad format, handled later. |
+ return false; |
+ } |
+ slice_end = Smi::Cast(object).Value(); |
+ } |
+ uint16_t char_limit = CharacterLimit(base, slice_start, slice_end); |
+ if (char_limit > 0xff) { |
+ return false; |
+ } |
+ } |
+ } |
+ return true; |
+} |
+ |
+ |
+DEFINE_NATIVE_ENTRY(StringBase_joinReplaceAllResult, 4) { |
+ const String& base = String::CheckedHandle(arguments->NativeArgAt(0)); |
+ GET_NON_NULL_NATIVE_ARGUMENT(GrowableObjectArray, |
+ matches_growable, arguments->NativeArgAt(1)); |
+ GET_NON_NULL_NATIVE_ARGUMENT(Smi, length_obj, arguments->NativeArgAt(2)); |
+ GET_NON_NULL_NATIVE_ARGUMENT(Bool, is_onebyte_obj, arguments->NativeArgAt(3)); |
+ |
+ intptr_t len = matches_growable.Length(); |
+ const Array& matches = Array::Handle(isolate, matches_growable.data()); |
+ |
+ const intptr_t length = length_obj.Value(); |
+ if (length < 0) { |
+ Exceptions::ThrowArgumentError(length_obj); |
+ } |
+ |
+ // Start out assuming result is one-byte if replacements are. |
+ bool is_onebyte = is_onebyte_obj.value(); |
+ if (is_onebyte) { |
+ // If any of the base string slices are not one-byte, the result will be |
+ // a two-byte string. |
+ if (!base.IsOneByteString() && !base.IsExternalOneByteString()) { |
+ is_onebyte = CheckSlicesOneByte(base, matches, len); |
+ } |
+ } |
+ |
+ const intptr_t base_length = base.Length(); |
+ String& result = String::Handle(isolate); |
+ if (is_onebyte) { |
+ result ^= OneByteString::New(length, Heap::kNew); |
+ } else { |
+ result ^= TwoByteString::New(length, Heap::kNew); |
+ } |
+ Instance& object = Instance::Handle(isolate); |
+ intptr_t write_index = 0; |
+ for (intptr_t i = 0; i < len; i++) { |
+ object ^= matches.At(i); |
+ if (object.IsSmi()) { |
+ intptr_t slice_start = Smi::Cast(object).Value(); |
+ intptr_t slice_length = -1; |
+ // Slices with limited ranges are stored in a single negative Smi. |
+ if (slice_start < 0) { |
+ intptr_t bits = -slice_start; |
+ slice_start = bits >> kLengthSize; |
+ slice_length = bits & kLengthMask; |
+ } else { |
+ i++; |
+ if (i < len) { // Otherwise slice_length stays at -1. |
+ object ^= matches.At(i); |
+ if (object.IsSmi()) { |
+ intptr_t slice_end = Smi::Cast(object).Value(); |
+ slice_length = slice_end - slice_start; |
+ } |
+ } |
+ } |
+ if (slice_length > 0) { |
+ if (0 <= slice_start && |
+ slice_start + slice_length <= base_length && |
+ write_index + slice_length <= length) { |
+ String::Copy(result, write_index, |
+ base, slice_start, |
+ slice_length); |
+ write_index += slice_length; |
+ continue; |
+ } |
+ } |
+ // Either the slice_length was zero, |
+ // or the first smi was positive and not followed by another smi, |
+ // or the smis were not a valid slice of the base string, |
+ // or the slice was too large to fit in the result. |
+ // Something is wrong with the matches array! |
+ Exceptions::ThrowArgumentError(matches_growable); |
+ } else if (object.IsString()) { |
+ const String& replacement = String::Cast(object); |
+ intptr_t replacement_length = replacement.Length(); |
+ if (write_index + replacement_length > length) { |
+ // Invalid input data, either in matches list or the total length. |
+ Exceptions::ThrowArgumentError(matches_growable); |
+ } |
+ String::Copy(result, write_index, replacement, 0, replacement_length); |
+ write_index += replacement_length; |
+ } |
+ } |
+ if (write_index < length) { |
+ Exceptions::ThrowArgumentError(matches_growable); |
+ } |
+ return result.raw(); |
+} |
+ |
DEFINE_NATIVE_ENTRY(OneByteString_substringUnchecked, 3) { |
const String& receiver = String::CheckedHandle(arguments->NativeArgAt(0)); |
ASSERT(receiver.IsOneByteString()); |