OLD | NEW |
1 // Copyright 2014 the V8 project authors. All rights reserved. | 1 // Copyright 2014 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "src/runtime/runtime-utils.h" | 5 #include "src/runtime/runtime-utils.h" |
6 | 6 |
7 #include "src/arguments.h" | 7 #include "src/arguments.h" |
8 #include "src/conversions-inl.h" | 8 #include "src/conversions-inl.h" |
9 #include "src/isolate-inl.h" | 9 #include "src/isolate-inl.h" |
10 #include "src/messages.h" | 10 #include "src/messages.h" |
11 #include "src/regexp/jsregexp-inl.h" | 11 #include "src/regexp/jsregexp-inl.h" |
12 #include "src/regexp/jsregexp.h" | 12 #include "src/regexp/jsregexp.h" |
| 13 #include "src/regexp/regexp-utils.h" |
13 #include "src/string-builder.h" | 14 #include "src/string-builder.h" |
14 #include "src/string-search.h" | 15 #include "src/string-search.h" |
15 | 16 |
16 namespace v8 { | 17 namespace v8 { |
17 namespace internal { | 18 namespace internal { |
18 | 19 |
19 class CompiledReplacement { | 20 class CompiledReplacement { |
20 public: | 21 public: |
21 explicit CompiledReplacement(Zone* zone) | 22 explicit CompiledReplacement(Zone* zone) |
22 : parts_(1, zone), replacement_substrings_(0, zone), zone_(zone) {} | 23 : parts_(1, zone), replacement_substrings_(0, zone), zone_(zone) {} |
(...skipping 763 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
786 } | 787 } |
787 | 788 |
788 | 789 |
789 RUNTIME_FUNCTION(Runtime_RegExpFlags) { | 790 RUNTIME_FUNCTION(Runtime_RegExpFlags) { |
790 SealHandleScope shs(isolate); | 791 SealHandleScope shs(isolate); |
791 DCHECK(args.length() == 1); | 792 DCHECK(args.length() == 1); |
792 CONVERT_ARG_CHECKED(JSRegExp, regexp, 0); | 793 CONVERT_ARG_CHECKED(JSRegExp, regexp, 0); |
793 return regexp->flags(); | 794 return regexp->flags(); |
794 } | 795 } |
795 | 796 |
796 | |
797 RUNTIME_FUNCTION(Runtime_RegExpSource) { | 797 RUNTIME_FUNCTION(Runtime_RegExpSource) { |
798 SealHandleScope shs(isolate); | 798 SealHandleScope shs(isolate); |
799 DCHECK(args.length() == 1); | 799 DCHECK(args.length() == 1); |
800 CONVERT_ARG_CHECKED(JSRegExp, regexp, 0); | 800 CONVERT_ARG_CHECKED(JSRegExp, regexp, 0); |
801 return regexp->source(); | 801 return regexp->source(); |
802 } | 802 } |
803 | 803 |
804 // TODO(jgruber): Remove this once all uses in regexp.js have been removed. | 804 // TODO(jgruber): Remove this once all uses in regexp.js have been removed. |
805 RUNTIME_FUNCTION(Runtime_RegExpConstructResult) { | 805 RUNTIME_FUNCTION(Runtime_RegExpConstructResult) { |
806 HandleScope handle_scope(isolate); | 806 HandleScope handle_scope(isolate); |
807 DCHECK(args.length() == 3); | 807 DCHECK(args.length() == 3); |
808 CONVERT_SMI_ARG_CHECKED(size, 0); | 808 CONVERT_SMI_ARG_CHECKED(size, 0); |
809 CHECK(size >= 0 && size <= FixedArray::kMaxLength); | 809 CHECK(size >= 0 && size <= FixedArray::kMaxLength); |
810 CONVERT_ARG_HANDLE_CHECKED(Object, index, 1); | 810 CONVERT_ARG_HANDLE_CHECKED(Object, index, 1); |
811 CONVERT_ARG_HANDLE_CHECKED(Object, input, 2); | 811 CONVERT_ARG_HANDLE_CHECKED(Object, input, 2); |
812 Handle<FixedArray> elements = isolate->factory()->NewFixedArray(size); | 812 Handle<FixedArray> elements = isolate->factory()->NewFixedArray(size); |
813 Handle<Map> regexp_map(isolate->native_context()->regexp_result_map()); | 813 Handle<Map> regexp_map(isolate->native_context()->regexp_result_map()); |
814 Handle<JSObject> object = | 814 Handle<JSObject> object = |
815 isolate->factory()->NewJSObjectFromMap(regexp_map, NOT_TENURED); | 815 isolate->factory()->NewJSObjectFromMap(regexp_map, NOT_TENURED); |
816 Handle<JSArray> array = Handle<JSArray>::cast(object); | 816 Handle<JSArray> array = Handle<JSArray>::cast(object); |
817 array->set_elements(*elements); | 817 array->set_elements(*elements); |
818 array->set_length(Smi::FromInt(size)); | 818 array->set_length(Smi::FromInt(size)); |
819 // Write in-object properties after the length of the array. | 819 // Write in-object properties after the length of the array. |
820 array->InObjectPropertyAtPut(JSRegExpResult::kIndexIndex, *index); | 820 array->InObjectPropertyAtPut(JSRegExpResult::kIndexIndex, *index); |
821 array->InObjectPropertyAtPut(JSRegExpResult::kInputIndex, *input); | 821 array->InObjectPropertyAtPut(JSRegExpResult::kInputIndex, *input); |
822 return *array; | 822 return *array; |
823 } | 823 } |
824 | 824 |
825 | |
826 RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) { | 825 RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) { |
827 HandleScope scope(isolate); | 826 HandleScope scope(isolate); |
828 DCHECK(args.length() == 3); | 827 DCHECK(args.length() == 3); |
829 CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0); | 828 CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0); |
830 CONVERT_ARG_HANDLE_CHECKED(String, source, 1); | 829 CONVERT_ARG_HANDLE_CHECKED(String, source, 1); |
831 CONVERT_ARG_HANDLE_CHECKED(String, flags, 2); | 830 CONVERT_ARG_HANDLE_CHECKED(String, flags, 2); |
832 | 831 |
833 RETURN_FAILURE_ON_EXCEPTION(isolate, | 832 RETURN_FAILURE_ON_EXCEPTION(isolate, |
834 JSRegExp::Initialize(regexp, source, flags)); | 833 JSRegExp::Initialize(regexp, source, flags)); |
835 | 834 |
836 return *regexp; | 835 return *regexp; |
837 } | 836 } |
838 | 837 |
| 838 namespace { |
839 | 839 |
840 // Only called from Runtime_RegExpExecMultiple so it doesn't need to maintain | 840 class MatchInfoBackedMatch : public String::Match { |
| 841 public: |
| 842 MatchInfoBackedMatch(Isolate* isolate, Handle<String> subject, |
| 843 Handle<JSObject> match_info) |
| 844 : isolate_(isolate), match_info_(match_info) { |
| 845 subject_ = String::Flatten(subject); |
| 846 } |
| 847 |
| 848 Handle<String> GetMatch() override { |
| 849 return RegExpUtils::GenericCaptureGetter(isolate_, match_info_, 0, nullptr); |
| 850 } |
| 851 |
| 852 MaybeHandle<String> GetCapture(int i, bool* capture_exists) override { |
| 853 Handle<Object> capture_obj = |
| 854 RegExpUtils::GenericCaptureGetter(isolate_, match_info_, i); |
| 855 if (capture_obj->IsUndefined(isolate_)) { |
| 856 *capture_exists = false; |
| 857 return isolate_->factory()->empty_string(); |
| 858 } |
| 859 *capture_exists = true; |
| 860 return Object::ToString(isolate_, capture_obj); |
| 861 } |
| 862 |
| 863 Handle<String> GetPrefix() override { |
| 864 const int match_start = |
| 865 RegExpUtils::GetLastMatchCapture(isolate_, match_info_, 0); |
| 866 return isolate_->factory()->NewSubString(subject_, 0, match_start); |
| 867 } |
| 868 |
| 869 Handle<String> GetSuffix() override { |
| 870 const int match_end = |
| 871 RegExpUtils::GetLastMatchCapture(isolate_, match_info_, 1); |
| 872 return isolate_->factory()->NewSubString(subject_, match_end, |
| 873 subject_->length()); |
| 874 } |
| 875 |
| 876 int CaptureCount() override { |
| 877 return RegExpUtils::GetLastMatchNumberOfCaptures(isolate_, match_info_) / 2; |
| 878 } |
| 879 |
| 880 virtual ~MatchInfoBackedMatch() {} |
| 881 |
| 882 private: |
| 883 Isolate* isolate_; |
| 884 Handle<String> subject_; |
| 885 Handle<JSObject> match_info_; |
| 886 }; |
| 887 |
| 888 class VectorBackedMatch : public String::Match { |
| 889 public: |
| 890 VectorBackedMatch(Isolate* isolate, Handle<String> subject, |
| 891 Handle<String> match, int match_position, |
| 892 ZoneVector<Handle<Object>>* captures) |
| 893 : isolate_(isolate), |
| 894 match_(match), |
| 895 match_position_(match_position), |
| 896 captures_(captures) { |
| 897 subject_ = String::Flatten(subject); |
| 898 } |
| 899 |
| 900 Handle<String> GetMatch() override { return match_; } |
| 901 |
| 902 MaybeHandle<String> GetCapture(int i, bool* capture_exists) override { |
| 903 Handle<Object> capture_obj = captures_->at(i); |
| 904 if (capture_obj->IsUndefined(isolate_)) { |
| 905 *capture_exists = false; |
| 906 return isolate_->factory()->empty_string(); |
| 907 } |
| 908 *capture_exists = true; |
| 909 return Object::ToString(isolate_, capture_obj); |
| 910 } |
| 911 |
| 912 Handle<String> GetPrefix() override { |
| 913 return isolate_->factory()->NewSubString(subject_, 0, match_position_); |
| 914 } |
| 915 |
| 916 Handle<String> GetSuffix() override { |
| 917 const int match_end_position = match_position_ + match_->length(); |
| 918 return isolate_->factory()->NewSubString(subject_, match_end_position, |
| 919 subject_->length()); |
| 920 } |
| 921 |
| 922 int CaptureCount() override { return static_cast<int>(captures_->size()); } |
| 923 |
| 924 virtual ~VectorBackedMatch() {} |
| 925 |
| 926 private: |
| 927 Isolate* isolate_; |
| 928 Handle<String> subject_; |
| 929 Handle<String> match_; |
| 930 const int match_position_; |
| 931 ZoneVector<Handle<Object>>* captures_; |
| 932 }; |
| 933 |
| 934 // Only called from RegExpExecMultiple so it doesn't need to maintain |
841 // separate last match info. See comment on that function. | 935 // separate last match info. See comment on that function. |
842 template <bool has_capture> | 936 template <bool has_capture> |
843 static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject, | 937 MaybeHandle<Object> SearchRegExpMultiple(Isolate* isolate, |
844 Handle<JSRegExp> regexp, | 938 Handle<String> subject, |
845 Handle<JSObject> last_match_array, | 939 Handle<JSRegExp> regexp, |
846 Handle<JSArray> result_array) { | 940 Handle<JSObject> last_match_array, |
| 941 Handle<FixedArray> result_elements) { |
847 DCHECK(subject->IsFlat()); | 942 DCHECK(subject->IsFlat()); |
848 DCHECK_NE(has_capture, regexp->CaptureCount() == 0); | 943 DCHECK_NE(has_capture, regexp->CaptureCount() == 0); |
849 | 944 |
850 int capture_count = regexp->CaptureCount(); | 945 int capture_count = regexp->CaptureCount(); |
851 int subject_length = subject->length(); | 946 int subject_length = subject->length(); |
852 | 947 |
853 static const int kMinLengthToCache = 0x1000; | 948 static const int kMinLengthToCache = 0x1000; |
854 | 949 |
855 if (subject_length > kMinLengthToCache) { | 950 if (subject_length > kMinLengthToCache) { |
856 FixedArray* last_match_cache; | 951 FixedArray* last_match_cache; |
857 Object* cached_answer = RegExpResultsCache::Lookup( | 952 Object* cached_answer = RegExpResultsCache::Lookup( |
858 isolate->heap(), *subject, regexp->data(), &last_match_cache, | 953 isolate->heap(), *subject, regexp->data(), &last_match_cache, |
859 RegExpResultsCache::REGEXP_MULTIPLE_INDICES); | 954 RegExpResultsCache::REGEXP_MULTIPLE_INDICES); |
860 if (cached_answer->IsFixedArray()) { | 955 if (cached_answer->IsFixedArray()) { |
861 int capture_registers = (capture_count + 1) * 2; | 956 int capture_registers = (capture_count + 1) * 2; |
862 int32_t* last_match = NewArray<int32_t>(capture_registers); | 957 int32_t* last_match = NewArray<int32_t>(capture_registers); |
863 for (int i = 0; i < capture_registers; i++) { | 958 for (int i = 0; i < capture_registers; i++) { |
864 last_match[i] = Smi::cast(last_match_cache->get(i))->value(); | 959 last_match[i] = Smi::cast(last_match_cache->get(i))->value(); |
865 } | 960 } |
866 Handle<FixedArray> cached_fixed_array = | 961 Handle<FixedArray> cached_fixed_array(FixedArray::cast(cached_answer)); |
867 Handle<FixedArray>(FixedArray::cast(cached_answer)); | |
868 // The cache FixedArray is a COW-array and can therefore be reused. | |
869 JSArray::SetContent(result_array, cached_fixed_array); | |
870 RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count, | 962 RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count, |
871 last_match); | 963 last_match); |
872 DeleteArray(last_match); | 964 DeleteArray(last_match); |
873 return *result_array; | 965 // The cache FixedArray is a COW-array and we need to return a copy. |
| 966 return isolate->factory()->CopyFixedArrayWithMap( |
| 967 cached_fixed_array, isolate->factory()->fixed_array_map()); |
874 } | 968 } |
875 } | 969 } |
876 | 970 |
877 RegExpImpl::GlobalCache global_cache(regexp, subject, isolate); | 971 RegExpImpl::GlobalCache global_cache(regexp, subject, isolate); |
878 if (global_cache.HasException()) return isolate->heap()->exception(); | 972 if (global_cache.HasException()) return MaybeHandle<Object>(); |
879 | 973 |
880 // Ensured in Runtime_RegExpExecMultiple. | 974 // Ensured in Runtime_RegExpExecMultiple. |
881 DCHECK(result_array->HasFastObjectElements()); | |
882 Handle<FixedArray> result_elements( | |
883 FixedArray::cast(result_array->elements())); | |
884 if (result_elements->length() < 16) { | 975 if (result_elements->length() < 16) { |
885 result_elements = isolate->factory()->NewFixedArrayWithHoles(16); | 976 result_elements = isolate->factory()->NewFixedArrayWithHoles(16); |
886 } | 977 } |
887 | 978 |
888 FixedArrayBuilder builder(result_elements); | 979 FixedArrayBuilder builder(result_elements); |
889 | 980 |
890 // Position to search from. | 981 // Position to search from. |
891 int match_start = -1; | 982 int match_start = -1; |
892 int match_end = 0; | 983 int match_end = 0; |
893 bool first = true; | 984 bool first = true; |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
940 } | 1031 } |
941 elements->set(capture_count + 1, Smi::FromInt(match_start)); | 1032 elements->set(capture_count + 1, Smi::FromInt(match_start)); |
942 elements->set(capture_count + 2, *subject); | 1033 elements->set(capture_count + 2, *subject); |
943 builder.Add(*isolate->factory()->NewJSArrayWithElements(elements)); | 1034 builder.Add(*isolate->factory()->NewJSArrayWithElements(elements)); |
944 } else { | 1035 } else { |
945 builder.Add(*match); | 1036 builder.Add(*match); |
946 } | 1037 } |
947 } | 1038 } |
948 } | 1039 } |
949 | 1040 |
950 if (global_cache.HasException()) return isolate->heap()->exception(); | 1041 if (global_cache.HasException()) return MaybeHandle<Object>(); |
951 | 1042 |
952 if (match_start >= 0) { | 1043 if (match_start >= 0) { |
953 // Finished matching, with at least one match. | 1044 // Finished matching, with at least one match. |
954 if (match_end < subject_length) { | 1045 if (match_end < subject_length) { |
955 ReplacementStringBuilder::AddSubjectSlice(&builder, match_end, | 1046 ReplacementStringBuilder::AddSubjectSlice(&builder, match_end, |
956 subject_length); | 1047 subject_length); |
957 } | 1048 } |
958 | 1049 |
959 RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count, | 1050 RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count, |
960 global_cache.LastSuccessfulMatch()); | 1051 global_cache.LastSuccessfulMatch()); |
961 | 1052 |
| 1053 Handle<FixedArray> result_fixed_array = builder.array(); |
| 1054 result_fixed_array->Shrink(builder.length()); |
| 1055 |
962 if (subject_length > kMinLengthToCache) { | 1056 if (subject_length > kMinLengthToCache) { |
963 // Store the last successful match into the array for caching. | 1057 // Store the last successful match into the array for caching. |
964 // TODO(yangguo): do not expose last match to JS and simplify caching. | 1058 // TODO(yangguo): do not expose last match to JS and simplify caching. |
965 int capture_registers = (capture_count + 1) * 2; | 1059 int capture_registers = (capture_count + 1) * 2; |
966 Handle<FixedArray> last_match_cache = | 1060 Handle<FixedArray> last_match_cache = |
967 isolate->factory()->NewFixedArray(capture_registers); | 1061 isolate->factory()->NewFixedArray(capture_registers); |
968 int32_t* last_match = global_cache.LastSuccessfulMatch(); | 1062 int32_t* last_match = global_cache.LastSuccessfulMatch(); |
969 for (int i = 0; i < capture_registers; i++) { | 1063 for (int i = 0; i < capture_registers; i++) { |
970 last_match_cache->set(i, Smi::FromInt(last_match[i])); | 1064 last_match_cache->set(i, Smi::FromInt(last_match[i])); |
971 } | 1065 } |
972 Handle<FixedArray> result_fixed_array = builder.array(); | |
973 result_fixed_array->Shrink(builder.length()); | |
974 // Cache the result and turn the FixedArray into a COW array. | 1066 // Cache the result and turn the FixedArray into a COW array. |
975 RegExpResultsCache::Enter( | 1067 RegExpResultsCache::Enter( |
976 isolate, subject, handle(regexp->data(), isolate), result_fixed_array, | 1068 isolate, subject, handle(regexp->data(), isolate), result_fixed_array, |
977 last_match_cache, RegExpResultsCache::REGEXP_MULTIPLE_INDICES); | 1069 last_match_cache, RegExpResultsCache::REGEXP_MULTIPLE_INDICES); |
978 } | 1070 } |
979 return *builder.ToJSArray(result_array); | 1071 // The cache FixedArray is a COW-array and we need to return a copy. |
| 1072 return isolate->factory()->CopyFixedArrayWithMap( |
| 1073 result_fixed_array, isolate->factory()->fixed_array_map()); |
980 } else { | 1074 } else { |
981 return isolate->heap()->null_value(); // No matches at all. | 1075 return isolate->factory()->null_value(); // No matches at all. |
982 } | 1076 } |
983 } | 1077 } |
984 | 1078 |
985 | |
986 // This is only called for StringReplaceGlobalRegExpWithFunction. This sets | 1079 // This is only called for StringReplaceGlobalRegExpWithFunction. This sets |
987 // lastMatchInfoOverride to maintain the last match info, so we don't need to | 1080 // lastMatchInfoOverride to maintain the last match info, so we don't need to |
988 // set any other last match array info. | 1081 // set any other last match array info. |
989 RUNTIME_FUNCTION(Runtime_RegExpExecMultiple) { | 1082 MaybeHandle<Object> RegExpExecMultiple(Isolate* isolate, |
990 HandleScope handles(isolate); | 1083 Handle<JSRegExp> regexp, |
991 DCHECK(args.length() == 4); | 1084 Handle<String> subject, |
992 | 1085 Handle<JSObject> last_match_info, |
993 CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0); | 1086 Handle<FixedArray> result_array) { |
994 CONVERT_ARG_HANDLE_CHECKED(String, subject, 1); | |
995 CONVERT_ARG_HANDLE_CHECKED(JSObject, last_match_info, 2); | |
996 CONVERT_ARG_HANDLE_CHECKED(JSArray, result_array, 3); | |
997 CHECK(last_match_info->HasFastObjectElements()); | 1087 CHECK(last_match_info->HasFastObjectElements()); |
998 CHECK(result_array->HasFastObjectElements()); | |
999 | 1088 |
1000 subject = String::Flatten(subject); | 1089 subject = String::Flatten(subject); |
1001 CHECK(regexp->GetFlags() & JSRegExp::kGlobal); | 1090 CHECK(regexp->GetFlags() & JSRegExp::kGlobal); |
1002 | 1091 |
1003 if (regexp->CaptureCount() == 0) { | 1092 if (regexp->CaptureCount() == 0) { |
1004 return SearchRegExpMultiple<false>(isolate, subject, regexp, | 1093 return SearchRegExpMultiple<false>(isolate, subject, regexp, |
1005 last_match_info, result_array); | 1094 last_match_info, result_array); |
1006 } else { | 1095 } else { |
1007 return SearchRegExpMultiple<true>(isolate, subject, regexp, last_match_info, | 1096 return SearchRegExpMultiple<true>(isolate, subject, regexp, last_match_info, |
1008 result_array); | 1097 result_array); |
1009 } | 1098 } |
1010 } | 1099 } |
1011 | 1100 |
| 1101 // Helper function for replacing regular expressions with the result of a |
| 1102 // function application in String.prototype.replace. |
| 1103 MaybeHandle<String> StringReplaceGlobalRegExpWithFunction( |
| 1104 Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp, |
| 1105 Handle<Object> replace_obj) { |
| 1106 Factory* factory = isolate->factory(); |
| 1107 |
| 1108 // TODO(jgruber): Convert result_array into a List<Handle<Object>> (or |
| 1109 // similar) and adapt / remove FixedArrayBuilder. |
| 1110 Handle<JSObject> last_match_info = isolate->regexp_last_match_info(); |
| 1111 Handle<FixedArray> result_array = factory->NewFixedArrayWithHoles(16); |
| 1112 |
| 1113 Handle<Object> res; |
| 1114 ASSIGN_RETURN_ON_EXCEPTION(isolate, res, |
| 1115 RegExpExecMultiple(isolate, regexp, subject, |
| 1116 last_match_info, result_array), |
| 1117 String); |
| 1118 |
| 1119 // Reload the last match info since it might have changed in the meantime. |
| 1120 last_match_info = isolate->regexp_last_match_info(); |
| 1121 |
| 1122 if (res->IsNull(isolate)) return subject; // No matches at all. |
| 1123 |
| 1124 result_array = Handle<FixedArray>::cast(res); |
| 1125 const int result_length = result_array->length(); |
| 1126 |
| 1127 const int num_captures = |
| 1128 RegExpUtils::GetLastMatchNumberOfCaptures(isolate, last_match_info) / 2; |
| 1129 if (num_captures == 1) { |
| 1130 // If the number of captures is one then there are no explicit captures in |
| 1131 // the regexp, just the implicit capture that captures the whole match. In |
| 1132 // this case we can simplify quite a bit and end up with something faster. |
| 1133 // The builder will consist of some integers that indicate slices of the |
| 1134 // input string and some replacements that were returned from the replace |
| 1135 // function. |
| 1136 int match_start = 0; |
| 1137 for (int i = 0; i < result_length; i++) { |
| 1138 Handle<Object> elem = FixedArray::get(*result_array, i, isolate); |
| 1139 if (elem->IsSmi()) { |
| 1140 // Integers represent slices of the original string. |
| 1141 // TODO(jgruber): Maybe we don't need this weird encoding anymore (in |
| 1142 // preparation to invoking StringBuilderConcat), but can just copy into |
| 1143 // the result string with the IncrementalStringBuilder as we go? |
| 1144 const int elem_value = Handle<Smi>::cast(elem)->value(); |
| 1145 if (elem_value > 0) { |
| 1146 match_start = (elem_value >> 11) + (elem_value & 0x7ff); |
| 1147 } else { |
| 1148 Handle<Object> next_elem = |
| 1149 FixedArray::get(*result_array, ++i, isolate); |
| 1150 const int next_elem_value = Handle<Smi>::cast(next_elem)->value(); |
| 1151 match_start = next_elem_value - elem_value; |
| 1152 } |
| 1153 } else { |
| 1154 DCHECK(elem->IsString()); |
| 1155 Handle<String> elem_string = Handle<String>::cast(elem); |
| 1156 |
| 1157 // Overwrite the i'th element in the results with the string we got |
| 1158 // back from the callback function. |
| 1159 const int argc = 3; |
| 1160 ScopedVector<Handle<Object>> argv(argc); |
| 1161 |
| 1162 argv[0] = elem_string; |
| 1163 argv[1] = handle(Smi::FromInt(match_start), isolate); |
| 1164 argv[2] = subject; |
| 1165 |
| 1166 Handle<Object> replacement_obj; |
| 1167 ASSIGN_RETURN_ON_EXCEPTION( |
| 1168 isolate, replacement_obj, |
| 1169 Execution::Call(isolate, replace_obj, factory->undefined_value(), |
| 1170 argc, argv.start()), |
| 1171 String); |
| 1172 |
| 1173 Handle<String> replacement; |
| 1174 ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement, |
| 1175 Object::ToString(isolate, replacement_obj), |
| 1176 String); |
| 1177 |
| 1178 result_array->set(i, *replacement); |
| 1179 match_start += elem_string->length(); |
| 1180 } |
| 1181 } |
| 1182 } else { |
| 1183 DCHECK(num_captures > 1); |
| 1184 for (int i = 0; i < result_length; i++) { |
| 1185 Handle<Object> elem = FixedArray::get(*result_array, i, isolate); |
| 1186 if (elem->IsSmi()) continue; |
| 1187 |
| 1188 // TODO(jgruber): We can skip this whole round-trip through a JS array |
| 1189 // for result_array. |
| 1190 Handle<JSArray> elem_array = Handle<JSArray>::cast(elem); |
| 1191 Handle<FixedArray> elem_array_elems( |
| 1192 FixedArray::cast(elem_array->elements()), isolate); |
| 1193 |
| 1194 const int argc = elem_array_elems->length(); |
| 1195 ScopedVector<Handle<Object>> argv(argc); |
| 1196 |
| 1197 for (int j = 0; j < argc; j++) { |
| 1198 argv[j] = FixedArray::get(*elem_array_elems, j, isolate); |
| 1199 } |
| 1200 |
| 1201 // TODO(jgruber): This call is another pattern we could refactor. |
| 1202 Handle<Object> replacement_obj; |
| 1203 ASSIGN_RETURN_ON_EXCEPTION( |
| 1204 isolate, replacement_obj, |
| 1205 Execution::Call(isolate, replace_obj, factory->undefined_value(), |
| 1206 argc, argv.start()), |
| 1207 String); |
| 1208 |
| 1209 Handle<String> replacement; |
| 1210 ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement, |
| 1211 Object::ToString(isolate, replacement_obj), |
| 1212 String); |
| 1213 |
| 1214 result_array->set(i, *replacement); |
| 1215 } |
| 1216 } |
| 1217 |
| 1218 if (result_length == 0) { |
| 1219 return factory->empty_string(); |
| 1220 } else if (result_length == 1) { |
| 1221 Handle<Object> first = FixedArray::get(*result_array, 0, isolate); |
| 1222 if (first->IsString()) return Handle<String>::cast(first); |
| 1223 } |
| 1224 |
| 1225 bool one_byte = subject->HasOnlyOneByteChars(); |
| 1226 const int length = StringBuilderConcatLength(subject->length(), *result_array, |
| 1227 result_length, &one_byte); |
| 1228 |
| 1229 if (length == -1) { |
| 1230 isolate->Throw(isolate->heap()->illegal_argument_string()); |
| 1231 return MaybeHandle<String>(); |
| 1232 } |
| 1233 |
| 1234 if (one_byte) { |
| 1235 Handle<SeqOneByteString> answer; |
| 1236 ASSIGN_RETURN_ON_EXCEPTION(isolate, answer, |
| 1237 isolate->factory()->NewRawOneByteString(length), |
| 1238 String); |
| 1239 StringBuilderConcatHelper(*subject, answer->GetChars(), *result_array, |
| 1240 result_length); |
| 1241 return answer; |
| 1242 } else { |
| 1243 DCHECK(!one_byte); |
| 1244 Handle<SeqTwoByteString> answer; |
| 1245 ASSIGN_RETURN_ON_EXCEPTION(isolate, answer, |
| 1246 isolate->factory()->NewRawTwoByteString(length), |
| 1247 String); |
| 1248 StringBuilderConcatHelper(*subject, answer->GetChars(), *result_array, |
| 1249 result_length); |
| 1250 return answer; |
| 1251 } |
| 1252 |
| 1253 UNREACHABLE(); |
| 1254 return MaybeHandle<String>(); |
| 1255 } |
| 1256 |
| 1257 MaybeHandle<String> StringReplaceNonGlobalRegExpWithFunction( |
| 1258 Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp, |
| 1259 Handle<Object> replace_obj) { |
| 1260 Factory* factory = isolate->factory(); |
| 1261 Handle<JSObject> last_match_info = isolate->regexp_last_match_info(); |
| 1262 |
| 1263 // TODO(jgruber): This is a pattern we could refactor. |
| 1264 Handle<Object> match_indices_obj; |
| 1265 ASSIGN_RETURN_ON_EXCEPTION( |
| 1266 isolate, match_indices_obj, |
| 1267 RegExpImpl::Exec(regexp, subject, 0, last_match_info), String); |
| 1268 |
| 1269 if (match_indices_obj->IsNull(isolate)) { |
| 1270 RETURN_ON_EXCEPTION(isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), |
| 1271 String); |
| 1272 return subject; |
| 1273 } |
| 1274 |
| 1275 Handle<JSObject> match_indices = Handle<JSObject>::cast(match_indices_obj); |
| 1276 |
| 1277 const int index = RegExpUtils::GetLastMatchCapture(isolate, match_indices, 0); |
| 1278 const int end_of_match = |
| 1279 RegExpUtils::GetLastMatchCapture(isolate, match_indices, 1); |
| 1280 |
| 1281 IncrementalStringBuilder builder(isolate); |
| 1282 builder.AppendString(factory->NewSubString(subject, 0, index)); |
| 1283 |
| 1284 // Compute the parameter list consisting of the match, captures, index, |
| 1285 // and subject for the replace function invocation. |
| 1286 // The number of captures plus one for the match. |
| 1287 const int m = |
| 1288 RegExpUtils::GetLastMatchNumberOfCaptures(isolate, match_indices) / 2; |
| 1289 |
| 1290 const int argc = m + 2; |
| 1291 ScopedVector<Handle<Object>> argv(argc); |
| 1292 |
| 1293 for (int j = 0; j < m; j++) { |
| 1294 bool ok; |
| 1295 Handle<String> capture = |
| 1296 RegExpUtils::GenericCaptureGetter(isolate, match_indices, j, &ok); |
| 1297 if (ok) { |
| 1298 argv[j] = capture; |
| 1299 } else { |
| 1300 argv[j] = factory->undefined_value(); |
| 1301 } |
| 1302 } |
| 1303 |
| 1304 argv[m] = handle(Smi::FromInt(index), isolate); |
| 1305 argv[m + 1] = subject; |
| 1306 |
| 1307 Handle<Object> replacement_obj; |
| 1308 ASSIGN_RETURN_ON_EXCEPTION( |
| 1309 isolate, replacement_obj, |
| 1310 Execution::Call(isolate, replace_obj, factory->undefined_value(), argc, |
| 1311 argv.start()), |
| 1312 String); |
| 1313 |
| 1314 Handle<String> replacement; |
| 1315 ASSIGN_RETURN_ON_EXCEPTION( |
| 1316 isolate, replacement, Object::ToString(isolate, replacement_obj), String); |
| 1317 |
| 1318 builder.AppendString(replacement); |
| 1319 builder.AppendString( |
| 1320 factory->NewSubString(subject, end_of_match, subject->length())); |
| 1321 |
| 1322 return builder.Finish(); |
| 1323 } |
| 1324 |
| 1325 // Legacy implementation of RegExp.prototype[Symbol.replace] which |
| 1326 // doesn't properly call the underlying exec method. |
| 1327 MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp, |
| 1328 Handle<String> string, |
| 1329 Handle<Object> replace_obj) { |
| 1330 Factory* factory = isolate->factory(); |
| 1331 |
| 1332 // TODO(jgruber): We need the even stricter guarantee of an unmodified |
| 1333 // JSRegExp map here for access to GetFlags to be legal. |
| 1334 const int flags = regexp->GetFlags(); |
| 1335 const bool global = (flags & JSRegExp::kGlobal) != 0; |
| 1336 |
| 1337 const bool functional_replace = replace_obj->IsCallable(); |
| 1338 if (!functional_replace) { |
| 1339 Handle<String> replace; |
| 1340 ASSIGN_RETURN_ON_EXCEPTION(isolate, replace, |
| 1341 Object::ToString(isolate, replace_obj), String); |
| 1342 replace = String::Flatten(replace); |
| 1343 |
| 1344 Handle<JSObject> last_match_info = isolate->regexp_last_match_info(); |
| 1345 |
| 1346 if (!global) { |
| 1347 // Non-global regexp search, string replace. |
| 1348 |
| 1349 Handle<Object> match_indices_obj; |
| 1350 ASSIGN_RETURN_ON_EXCEPTION( |
| 1351 isolate, match_indices_obj, |
| 1352 RegExpImpl::Exec(regexp, string, 0, last_match_info), String); |
| 1353 |
| 1354 if (match_indices_obj->IsNull(isolate)) { |
| 1355 RETURN_ON_EXCEPTION( |
| 1356 isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), String); |
| 1357 return string; |
| 1358 } |
| 1359 |
| 1360 auto match_indices = Handle<JSReceiver>::cast(match_indices_obj); |
| 1361 |
| 1362 Handle<Object> start_index_obj = |
| 1363 JSReceiver::GetElement(isolate, match_indices, |
| 1364 RegExpImpl::kFirstCapture) |
| 1365 .ToHandleChecked(); |
| 1366 const int start_index = Handle<Smi>::cast(start_index_obj)->value(); |
| 1367 |
| 1368 Handle<Object> end_index_obj = |
| 1369 JSReceiver::GetElement(isolate, match_indices, |
| 1370 RegExpImpl::kFirstCapture + 1) |
| 1371 .ToHandleChecked(); |
| 1372 const int end_index = Handle<Smi>::cast(end_index_obj)->value(); |
| 1373 |
| 1374 IncrementalStringBuilder builder(isolate); |
| 1375 builder.AppendString(factory->NewSubString(string, 0, start_index)); |
| 1376 |
| 1377 if (replace->length() > 0) { |
| 1378 MatchInfoBackedMatch m(isolate, string, last_match_info); |
| 1379 Handle<String> replacement; |
| 1380 ASSIGN_RETURN_ON_EXCEPTION( |
| 1381 isolate, replacement, String::GetSubstitution(isolate, &m, replace), |
| 1382 String); |
| 1383 builder.AppendString(replacement); |
| 1384 } |
| 1385 |
| 1386 builder.AppendString( |
| 1387 factory->NewSubString(string, end_index, string->length())); |
| 1388 return builder.Finish(); |
| 1389 } else { |
| 1390 // Global regexp search, string replace. |
| 1391 DCHECK(global); |
| 1392 RETURN_ON_EXCEPTION( |
| 1393 isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), String); |
| 1394 |
| 1395 if (replace->length() == 0) { |
| 1396 if (string->HasOnlyOneByteChars()) { |
| 1397 Object* result = |
| 1398 StringReplaceGlobalRegExpWithEmptyString<SeqOneByteString>( |
| 1399 isolate, string, regexp, last_match_info); |
| 1400 return handle(String::cast(result), isolate); |
| 1401 } else { |
| 1402 Object* result = |
| 1403 StringReplaceGlobalRegExpWithEmptyString<SeqTwoByteString>( |
| 1404 isolate, string, regexp, last_match_info); |
| 1405 return handle(String::cast(result), isolate); |
| 1406 } |
| 1407 } |
| 1408 |
| 1409 Object* result = StringReplaceGlobalRegExpWithString( |
| 1410 isolate, string, regexp, replace, last_match_info); |
| 1411 if (result->IsString()) { |
| 1412 return handle(String::cast(result), isolate); |
| 1413 } else { |
| 1414 return MaybeHandle<String>(); |
| 1415 } |
| 1416 } |
| 1417 } else { |
| 1418 DCHECK(functional_replace); |
| 1419 if (global) { |
| 1420 // Global regexp search, function replace. |
| 1421 return StringReplaceGlobalRegExpWithFunction(isolate, string, regexp, |
| 1422 replace_obj); |
| 1423 } else { |
| 1424 // Non-global regexp search, function replace. |
| 1425 return StringReplaceNonGlobalRegExpWithFunction(isolate, string, regexp, |
| 1426 replace_obj); |
| 1427 } |
| 1428 } |
| 1429 |
| 1430 UNREACHABLE(); |
| 1431 return MaybeHandle<String>(); |
| 1432 } |
| 1433 |
| 1434 } // namespace |
| 1435 |
| 1436 // Slow path for: |
| 1437 // ES#sec-regexp.prototype-@@replace |
| 1438 // RegExp.prototype [ @@replace ] ( string, replaceValue ) |
| 1439 RUNTIME_FUNCTION(Runtime_RegExpReplace) { |
| 1440 HandleScope scope(isolate); |
| 1441 DCHECK(args.length() == 3); |
| 1442 |
| 1443 CONVERT_ARG_HANDLE_CHECKED(JSReceiver, recv, 0); |
| 1444 CONVERT_ARG_HANDLE_CHECKED(String, string, 1); |
| 1445 Handle<Object> replace_obj = args.at<Object>(2); |
| 1446 |
| 1447 Factory* factory = isolate->factory(); |
| 1448 |
| 1449 string = String::Flatten(string); |
| 1450 |
| 1451 const int length = string->length(); |
| 1452 const bool functional_replace = replace_obj->IsCallable(); |
| 1453 |
| 1454 Handle<String> replace; |
| 1455 if (!functional_replace) { |
| 1456 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, replace, |
| 1457 Object::ToString(isolate, replace_obj)); |
| 1458 } |
| 1459 |
| 1460 Handle<Object> global_obj; |
| 1461 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1462 isolate, global_obj, |
| 1463 JSReceiver::GetProperty(recv, factory->global_string())); |
| 1464 const bool global = global_obj->BooleanValue(); |
| 1465 |
| 1466 bool unicode = false; |
| 1467 if (global) { |
| 1468 Handle<Object> unicode_obj; |
| 1469 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1470 isolate, unicode_obj, |
| 1471 JSReceiver::GetProperty(recv, factory->unicode_string())); |
| 1472 unicode = unicode_obj->BooleanValue(); |
| 1473 |
| 1474 RETURN_FAILURE_ON_EXCEPTION(isolate, |
| 1475 RegExpUtils::SetLastIndex(isolate, recv, 0)); |
| 1476 } |
| 1477 |
| 1478 // TODO(adamk): this fast path is wrong as we doesn't ensure that 'exec' |
| 1479 // is actually a data property on RegExp.prototype. |
| 1480 Handle<Object> exec = factory->undefined_value(); |
| 1481 if (recv->IsJSRegExp()) { |
| 1482 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1483 isolate, exec, JSObject::GetProperty( |
| 1484 recv, factory->NewStringFromAsciiChecked("exec"))); |
| 1485 if (RegExpUtils::IsBuiltinExec(exec)) { |
| 1486 RETURN_RESULT_OR_FAILURE( |
| 1487 isolate, RegExpReplace(isolate, Handle<JSRegExp>::cast(recv), string, |
| 1488 replace_obj)); |
| 1489 } |
| 1490 } |
| 1491 |
| 1492 Zone zone(isolate->allocator()); |
| 1493 ZoneVector<Handle<Object>> results(&zone); |
| 1494 |
| 1495 while (true) { |
| 1496 Handle<Object> result; |
| 1497 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1498 isolate, result, RegExpUtils::RegExpExec(isolate, recv, string, exec)); |
| 1499 |
| 1500 // Ensure exec will be read again on the next loop through. |
| 1501 exec = factory->undefined_value(); |
| 1502 |
| 1503 if (result->IsNull(isolate)) break; |
| 1504 |
| 1505 results.push_back(result); |
| 1506 if (!global) break; |
| 1507 |
| 1508 Handle<Object> match_obj; |
| 1509 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj, |
| 1510 Object::GetElement(isolate, result, 0)); |
| 1511 |
| 1512 Handle<String> match; |
| 1513 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match, |
| 1514 Object::ToString(isolate, match_obj)); |
| 1515 |
| 1516 if (match->length() == 0) { |
| 1517 RETURN_FAILURE_ON_EXCEPTION(isolate, RegExpUtils::SetAdvancedStringIndex( |
| 1518 isolate, recv, string, unicode)); |
| 1519 } |
| 1520 } |
| 1521 |
| 1522 // TODO(jgruber): Look into ReplacementStringBuilder instead. |
| 1523 IncrementalStringBuilder builder(isolate); |
| 1524 int next_source_position = 0; |
| 1525 |
| 1526 for (const auto& result : results) { |
| 1527 Handle<Object> captures_length_obj; |
| 1528 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1529 isolate, captures_length_obj, |
| 1530 Object::GetProperty(result, factory->length_string())); |
| 1531 |
| 1532 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1533 isolate, captures_length_obj, |
| 1534 Object::ToLength(isolate, captures_length_obj)); |
| 1535 const int captures_length = |
| 1536 std::max(Handle<Smi>::cast(captures_length_obj)->value(), 0); |
| 1537 |
| 1538 Handle<Object> match_obj; |
| 1539 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj, |
| 1540 Object::GetElement(isolate, result, 0)); |
| 1541 |
| 1542 Handle<String> match; |
| 1543 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match, |
| 1544 Object::ToString(isolate, match_obj)); |
| 1545 |
| 1546 const int match_length = match->length(); |
| 1547 |
| 1548 Handle<Object> position_obj; |
| 1549 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1550 isolate, position_obj, |
| 1551 Object::GetProperty(result, factory->index_string())); |
| 1552 |
| 1553 // TODO(jgruber): Extract and correct error handling. Since we can go up to |
| 1554 // 2^53 - 1 (at least for ToLength), we might actually need uint64_t here? |
| 1555 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1556 isolate, position_obj, Object::ToInteger(isolate, position_obj)); |
| 1557 const int position = |
| 1558 std::max(std::min(Handle<Smi>::cast(position_obj)->value(), length), 0); |
| 1559 |
| 1560 ZoneVector<Handle<Object>> captures(&zone); |
| 1561 for (int n = 0; n < captures_length; n++) { |
| 1562 Handle<Object> capture; |
| 1563 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1564 isolate, capture, Object::GetElement(isolate, result, n)); |
| 1565 |
| 1566 if (!capture->IsUndefined(isolate)) { |
| 1567 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, capture, |
| 1568 Object::ToString(isolate, capture)); |
| 1569 } |
| 1570 captures.push_back(capture); |
| 1571 } |
| 1572 |
| 1573 Handle<String> replacement; |
| 1574 if (functional_replace) { |
| 1575 const int argc = captures_length + 2; |
| 1576 ScopedVector<Handle<Object>> argv(argc); |
| 1577 |
| 1578 for (int j = 0; j < captures_length; j++) { |
| 1579 argv[j] = captures[j]; |
| 1580 } |
| 1581 |
| 1582 argv[captures_length] = handle(Smi::FromInt(position), isolate); |
| 1583 argv[captures_length + 1] = string; |
| 1584 |
| 1585 Handle<Object> replacement_obj; |
| 1586 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1587 isolate, replacement_obj, |
| 1588 Execution::Call(isolate, replace_obj, factory->undefined_value(), |
| 1589 argc, argv.start())); |
| 1590 |
| 1591 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1592 isolate, replacement, Object::ToString(isolate, replacement_obj)); |
| 1593 } else { |
| 1594 VectorBackedMatch m(isolate, string, match, position, &captures); |
| 1595 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| 1596 isolate, replacement, String::GetSubstitution(isolate, &m, replace)); |
| 1597 } |
| 1598 |
| 1599 if (position >= next_source_position) { |
| 1600 builder.AppendString( |
| 1601 factory->NewSubString(string, next_source_position, position)); |
| 1602 builder.AppendString(replacement); |
| 1603 |
| 1604 next_source_position = position + match_length; |
| 1605 } |
| 1606 } |
| 1607 |
| 1608 if (next_source_position < length) { |
| 1609 builder.AppendString( |
| 1610 factory->NewSubString(string, next_source_position, length)); |
| 1611 } |
| 1612 |
| 1613 RETURN_RESULT_OR_FAILURE(isolate, builder.Finish()); |
| 1614 } |
1012 | 1615 |
1013 RUNTIME_FUNCTION(Runtime_RegExpExecReThrow) { | 1616 RUNTIME_FUNCTION(Runtime_RegExpExecReThrow) { |
1014 SealHandleScope shs(isolate); | 1617 SealHandleScope shs(isolate); |
1015 DCHECK(args.length() == 4); | 1618 DCHECK(args.length() == 4); |
1016 Object* exception = isolate->pending_exception(); | 1619 Object* exception = isolate->pending_exception(); |
1017 isolate->clear_pending_exception(); | 1620 isolate->clear_pending_exception(); |
1018 return isolate->ReThrow(exception); | 1621 return isolate->ReThrow(exception); |
1019 } | 1622 } |
1020 | 1623 |
1021 | 1624 |
1022 RUNTIME_FUNCTION(Runtime_IsRegExp) { | 1625 RUNTIME_FUNCTION(Runtime_IsRegExp) { |
1023 SealHandleScope shs(isolate); | 1626 SealHandleScope shs(isolate); |
1024 DCHECK(args.length() == 1); | 1627 DCHECK(args.length() == 1); |
1025 CONVERT_ARG_CHECKED(Object, obj, 0); | 1628 CONVERT_ARG_CHECKED(Object, obj, 0); |
1026 return isolate->heap()->ToBoolean(obj->IsJSRegExp()); | 1629 return isolate->heap()->ToBoolean(obj->IsJSRegExp()); |
1027 } | 1630 } |
| 1631 |
1028 } // namespace internal | 1632 } // namespace internal |
1029 } // namespace v8 | 1633 } // namespace v8 |
OLD | NEW |