| OLD | NEW |
| 1 // Copyright 2015 the V8 project authors. All rights reserved. | 1 // Copyright 2015 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/wasm/module-decoder.h" | 5 #include "src/wasm/module-decoder.h" |
| 6 | 6 |
| 7 #include "src/base/functional.h" | 7 #include "src/base/functional.h" |
| 8 #include "src/base/platform/platform.h" | 8 #include "src/base/platform/platform.h" |
| 9 #include "src/flags.h" | 9 #include "src/flags.h" |
| 10 #include "src/macro-assembler.h" | 10 #include "src/macro-assembler.h" |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 } while (false) | 24 } while (false) |
| 25 #else | 25 #else |
| 26 #define TRACE(...) | 26 #define TRACE(...) |
| 27 #endif | 27 #endif |
| 28 | 28 |
| 29 namespace { | 29 namespace { |
| 30 | 30 |
| 31 const char* kNameString = "name"; | 31 const char* kNameString = "name"; |
| 32 const size_t kNameStringLength = 4; | 32 const size_t kNameStringLength = 4; |
| 33 | 33 |
| 34 const char* kAsmOffsetsString = "asm_offsets"; |
| 35 const size_t kAsmOffsetsLength = 11; |
| 36 |
| 34 LocalType TypeOf(const WasmModule* module, const WasmInitExpr& expr) { | 37 LocalType TypeOf(const WasmModule* module, const WasmInitExpr& expr) { |
| 35 switch (expr.kind) { | 38 switch (expr.kind) { |
| 36 case WasmInitExpr::kNone: | 39 case WasmInitExpr::kNone: |
| 37 return kAstStmt; | 40 return kAstStmt; |
| 38 case WasmInitExpr::kGlobalIndex: | 41 case WasmInitExpr::kGlobalIndex: |
| 39 return expr.val.global_index < module->globals.size() | 42 return expr.val.global_index < module->globals.size() |
| 40 ? module->globals[expr.val.global_index].type | 43 ? module->globals[expr.val.global_index].type |
| 41 : kAstStmt; | 44 : kAstStmt; |
| 42 case WasmInitExpr::kI32Const: | 45 case WasmInitExpr::kI32Const: |
| 43 return kAstI32; | 46 return kAstI32; |
| (...skipping 26 matching lines...) Expand all Loading... |
| 70 } | 73 } |
| 71 | 74 |
| 72 inline WasmSectionCode section_code() const { return section_code_; } | 75 inline WasmSectionCode section_code() const { return section_code_; } |
| 73 | 76 |
| 74 inline const byte* section_start() const { return section_start_; } | 77 inline const byte* section_start() const { return section_start_; } |
| 75 | 78 |
| 76 inline uint32_t section_length() const { | 79 inline uint32_t section_length() const { |
| 77 return static_cast<uint32_t>(section_end_ - section_start_); | 80 return static_cast<uint32_t>(section_end_ - section_start_); |
| 78 } | 81 } |
| 79 | 82 |
| 83 inline const byte* payload_start() const { return payload_start_; } |
| 84 |
| 85 inline uint32_t payload_length() const { |
| 86 return static_cast<uint32_t>(section_end_ - payload_start_); |
| 87 } |
| 88 |
| 80 inline const byte* section_end() const { return section_end_; } | 89 inline const byte* section_end() const { return section_end_; } |
| 81 | 90 |
| 82 // Advances to the next section, checking that decoding the current section | 91 // Advances to the next section, checking that decoding the current section |
| 83 // stopped at {section_end_}. | 92 // stopped at {section_end_}. |
| 84 void advance() { | 93 void advance() { |
| 85 if (decoder_.pc() != section_end_) { | 94 if (decoder_.pc() != section_end_) { |
| 86 const char* msg = decoder_.pc() < section_end_ ? "shorter" : "longer"; | 95 const char* msg = decoder_.pc() < section_end_ ? "shorter" : "longer"; |
| 87 decoder_.error(decoder_.pc(), decoder_.pc(), | 96 decoder_.error(decoder_.pc(), decoder_.pc(), |
| 88 "section was %s than expected size " | 97 "section was %s than expected size " |
| 89 "(%u bytes expected, %zu decoded)", | 98 "(%u bytes expected, %zu decoded)", |
| 90 msg, section_length(), | 99 msg, section_length(), |
| 91 static_cast<size_t>(decoder_.pc() - section_start_)); | 100 static_cast<size_t>(decoder_.pc() - section_start_)); |
| 92 } | 101 } |
| 93 next(); | 102 next(); |
| 94 } | 103 } |
| 95 | 104 |
| 96 private: | 105 private: |
| 97 Decoder& decoder_; | 106 Decoder& decoder_; |
| 98 WasmSectionCode section_code_; | 107 WasmSectionCode section_code_; |
| 99 const byte* section_start_; | 108 const byte* section_start_; |
| 109 const byte* payload_start_; |
| 100 const byte* section_end_; | 110 const byte* section_end_; |
| 101 | 111 |
| 102 // Reads the section code/name at the current position and sets up | 112 // Reads the section code/name at the current position and sets up |
| 103 // the internal fields. | 113 // the internal fields. |
| 104 void next() { | 114 void next() { |
| 105 while (true) { | 115 while (true) { |
| 106 if (!decoder_.more()) { | 116 if (!decoder_.more()) { |
| 107 section_code_ = kUnknownSectionCode; | 117 section_code_ = kUnknownSectionCode; |
| 108 return; | 118 return; |
| 109 } | 119 } |
| 110 uint8_t section_code = decoder_.consume_u8("section code"); | 120 uint8_t section_code = decoder_.consume_u8("section code"); |
| 111 // Read and check the section size. | 121 // Read and check the section size. |
| 112 uint32_t section_length = decoder_.consume_u32v("section length"); | 122 uint32_t section_length = decoder_.consume_u32v("section length"); |
| 113 section_start_ = decoder_.pc(); | 123 section_start_ = decoder_.pc(); |
| 124 payload_start_ = section_start_; |
| 114 if (decoder_.checkAvailable(section_length)) { | 125 if (decoder_.checkAvailable(section_length)) { |
| 115 // Get the limit of the section within the module. | 126 // Get the limit of the section within the module. |
| 116 section_end_ = section_start_ + section_length; | 127 section_end_ = section_start_ + section_length; |
| 117 } else { | 128 } else { |
| 118 // The section would extend beyond the end of the module. | 129 // The section would extend beyond the end of the module. |
| 119 section_end_ = section_start_; | 130 section_end_ = section_start_; |
| 120 } | 131 } |
| 121 | 132 |
| 122 if (section_code == kUnknownSectionCode) { | 133 if (section_code == kUnknownSectionCode) { |
| 123 // Check for the known "names" section. | 134 // Check for the known "name" or "asm_offsets" section. |
| 124 uint32_t string_length = decoder_.consume_u32v("section name length"); | 135 uint32_t string_length = decoder_.consume_u32v("section name length"); |
| 125 const byte* section_name_start = decoder_.pc(); | 136 const byte* section_name_start = decoder_.pc(); |
| 126 decoder_.consume_bytes(string_length, "section name"); | 137 decoder_.consume_bytes(string_length, "section name"); |
| 127 if (decoder_.failed() || decoder_.pc() > section_end_) { | 138 if (decoder_.failed() || decoder_.pc() > section_end_) { |
| 128 TRACE("Section name of length %u couldn't be read\n", string_length); | 139 TRACE("Section name of length %u couldn't be read\n", string_length); |
| 129 section_code_ = kUnknownSectionCode; | 140 section_code_ = kUnknownSectionCode; |
| 130 return; | 141 return; |
| 131 } | 142 } |
| 143 payload_start_ = decoder_.pc(); |
| 132 | 144 |
| 133 TRACE(" +%d section name : \"%.*s\"\n", | 145 TRACE(" +%d section name : \"%.*s\"\n", |
| 134 static_cast<int>(section_name_start - decoder_.start()), | 146 static_cast<int>(section_name_start - decoder_.start()), |
| 135 string_length < 20 ? string_length : 20, section_name_start); | 147 string_length < 20 ? string_length : 20, section_name_start); |
| 136 | 148 |
| 137 if (string_length == kNameStringLength && | 149 if (string_length == kNameStringLength && |
| 138 strncmp(reinterpret_cast<const char*>(section_name_start), | 150 memcmp(reinterpret_cast<const char*>(section_name_start), |
| 139 kNameString, kNameStringLength) == 0) { | 151 kNameString, kNameStringLength) == 0) { |
| 140 section_code = kNameSectionCode; | 152 section_code = kNameSectionCode; |
| 153 } else if (string_length == kAsmOffsetsLength && |
| 154 memcmp(reinterpret_cast<const char*>(section_name_start), |
| 155 kAsmOffsetsString, kAsmOffsetsLength) == 0) { |
| 156 section_code = kAsmOffsetsSectionCode; |
| 141 } else { | 157 } else { |
| 142 section_code = kUnknownSectionCode; | 158 section_code = kUnknownSectionCode; |
| 143 } | 159 } |
| 144 } else if (!IsValidSectionCode(section_code)) { | 160 } else if (!IsValidSectionCode(section_code)) { |
| 145 decoder_.error(decoder_.pc(), decoder_.pc(), | 161 decoder_.error(decoder_.pc(), decoder_.pc(), |
| 146 "unknown section code #0x%02x", section_code); | 162 "unknown section code #0x%02x", section_code); |
| 147 section_code = kUnknownSectionCode; | 163 section_code = kUnknownSectionCode; |
| 148 } | 164 } |
| 149 section_code_ = static_cast<WasmSectionCode>(section_code); | 165 section_code_ = static_cast<WasmSectionCode>(section_code); |
| 150 | 166 |
| (...skipping 432 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 583 functions_count, module->num_declared_functions); | 599 functions_count, module->num_declared_functions); |
| 584 } | 600 } |
| 585 | 601 |
| 586 for (uint32_t i = 0; ok() && i < functions_count; ++i) { | 602 for (uint32_t i = 0; ok() && i < functions_count; ++i) { |
| 587 WasmFunction* function = | 603 WasmFunction* function = |
| 588 &module->functions[i + module->num_imported_functions]; | 604 &module->functions[i + module->num_imported_functions]; |
| 589 function->name_offset = consume_string(&function->name_length, false); | 605 function->name_offset = consume_string(&function->name_length, false); |
| 590 | 606 |
| 591 uint32_t local_names_count = consume_u32v("local names count"); | 607 uint32_t local_names_count = consume_u32v("local names count"); |
| 592 for (uint32_t j = 0; ok() && j < local_names_count; j++) { | 608 for (uint32_t j = 0; ok() && j < local_names_count; j++) { |
| 593 uint32_t unused = 0; | 609 skip_string(); |
| 594 uint32_t offset = consume_string(&unused, false); | |
| 595 USE(unused); | |
| 596 USE(offset); | |
| 597 } | 610 } |
| 598 } | 611 } |
| 599 section_iter.advance(); | 612 section_iter.advance(); |
| 600 } | 613 } |
| 601 | 614 |
| 615 // ===== Wasm to asm.js offset table ===================================== |
| 616 if (section_iter.section_code() == kAsmOffsetsSectionCode) { |
| 617 const byte* pos = pc_; |
| 618 uint32_t functions_count = consume_u32v("functions count"); |
| 619 if (functions_count != module->num_declared_functions) { |
| 620 error(pos, pos, "function name count %u mismatch (%u expected)", |
| 621 functions_count, module->num_declared_functions); |
| 622 } |
| 623 |
| 624 for (uint32_t i = 0; ok() && i < functions_count; ++i) { |
| 625 // Do not store anything yet. Data will be decoded on-demand in |
| 626 // WasmDebugInfo. |
| 627 skip_string(); |
| 628 } |
| 629 section_iter.advance(); |
| 630 } |
| 631 |
| 602 // ===== Remaining sections ============================================== | 632 // ===== Remaining sections ============================================== |
| 603 if (section_iter.more() && ok()) { | 633 if (section_iter.more() && ok()) { |
| 604 error(pc(), pc(), "unexpected section: %s", | 634 error(pc(), pc(), "unexpected section: %s", |
| 605 SectionName(section_iter.section_code())); | 635 SectionName(section_iter.section_code())); |
| 606 } | 636 } |
| 607 | 637 |
| 608 if (ok()) { | 638 if (ok()) { |
| 609 CalculateGlobalOffsets(module); | 639 CalculateGlobalOffsets(module); |
| 610 PreinitializeIndirectFunctionTables(module); | 640 PreinitializeIndirectFunctionTables(module); |
| 611 } | 641 } |
| (...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 792 const byte* string_start = pc_; | 822 const byte* string_start = pc_; |
| 793 // Consume bytes before validation to guarantee that the string is not oob. | 823 // Consume bytes before validation to guarantee that the string is not oob. |
| 794 if (*length > 0) consume_bytes(*length, "string"); | 824 if (*length > 0) consume_bytes(*length, "string"); |
| 795 if (ok() && validate_utf8 && | 825 if (ok() && validate_utf8 && |
| 796 !unibrow::Utf8::Validate(string_start, *length)) { | 826 !unibrow::Utf8::Validate(string_start, *length)) { |
| 797 error(string_start, "no valid UTF-8 string"); | 827 error(string_start, "no valid UTF-8 string"); |
| 798 } | 828 } |
| 799 return offset; | 829 return offset; |
| 800 } | 830 } |
| 801 | 831 |
| 832 // Skips over a length-prefixed string, but checks that it is within bounds. |
| 833 void skip_string() { |
| 834 uint32_t length = consume_u32v("string length"); |
| 835 consume_bytes(length, "string"); |
| 836 } |
| 837 |
| 802 uint32_t consume_sig_index(WasmModule* module, FunctionSig** sig) { | 838 uint32_t consume_sig_index(WasmModule* module, FunctionSig** sig) { |
| 803 const byte* pos = pc_; | 839 const byte* pos = pc_; |
| 804 uint32_t sig_index = consume_u32v("signature index"); | 840 uint32_t sig_index = consume_u32v("signature index"); |
| 805 if (sig_index >= module->signatures.size()) { | 841 if (sig_index >= module->signatures.size()) { |
| 806 error(pos, pos, "signature index %u out of bounds (%d signatures)", | 842 error(pos, pos, "signature index %u out of bounds (%d signatures)", |
| 807 sig_index, static_cast<int>(module->signatures.size())); | 843 sig_index, static_cast<int>(module->signatures.size())); |
| 808 *sig = nullptr; | 844 *sig = nullptr; |
| 809 return 0; | 845 return 0; |
| 810 } | 846 } |
| 811 *sig = module->signatures[sig_index]; | 847 *sig = module->signatures[sig_index]; |
| (...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1022 explicit FunctionError(const char* msg) { | 1058 explicit FunctionError(const char* msg) { |
| 1023 error_code = kError; | 1059 error_code = kError; |
| 1024 size_t len = strlen(msg) + 1; | 1060 size_t len = strlen(msg) + 1; |
| 1025 char* result = new char[len]; | 1061 char* result = new char[len]; |
| 1026 strncpy(result, msg, len); | 1062 strncpy(result, msg, len); |
| 1027 result[len - 1] = 0; | 1063 result[len - 1] = 0; |
| 1028 error_msg.reset(result); | 1064 error_msg.reset(result); |
| 1029 } | 1065 } |
| 1030 }; | 1066 }; |
| 1031 | 1067 |
| 1068 // Find section with given section code. Return Vector of the payload, or null |
| 1069 // Vector if section is not found or module bytes are invalid. |
| 1032 Vector<const byte> FindSection(const byte* module_start, const byte* module_end, | 1070 Vector<const byte> FindSection(const byte* module_start, const byte* module_end, |
| 1033 WasmSectionCode code) { | 1071 WasmSectionCode code) { |
| 1034 Decoder decoder(module_start, module_end); | 1072 Decoder decoder(module_start, module_end); |
| 1035 | 1073 |
| 1036 uint32_t magic_word = decoder.consume_u32("wasm magic"); | 1074 uint32_t magic_word = decoder.consume_u32("wasm magic"); |
| 1037 if (magic_word != kWasmMagic) decoder.error("wrong magic word"); | 1075 if (magic_word != kWasmMagic) decoder.error("wrong magic word"); |
| 1038 | 1076 |
| 1039 uint32_t magic_version = decoder.consume_u32("wasm version"); | 1077 uint32_t magic_version = decoder.consume_u32("wasm version"); |
| 1040 if (magic_version != kWasmVersion) decoder.error("wrong wasm version"); | 1078 if (magic_version != kWasmVersion) decoder.error("wrong wasm version"); |
| 1041 | 1079 |
| 1042 WasmSectionIterator section_iter(decoder); | 1080 WasmSectionIterator section_iter(decoder); |
| 1043 while (section_iter.more()) { | 1081 while (section_iter.more()) { |
| 1044 if (section_iter.section_code() == code) { | 1082 if (section_iter.section_code() == code) { |
| 1045 return Vector<const uint8_t>(section_iter.section_start(), | 1083 return Vector<const uint8_t>(section_iter.payload_start(), |
| 1046 section_iter.section_length()); | 1084 section_iter.payload_length()); |
| 1047 } | 1085 } |
| 1048 decoder.consume_bytes(section_iter.section_length(), "section payload"); | 1086 decoder.consume_bytes(section_iter.payload_length(), "section payload"); |
| 1049 section_iter.advance(); | 1087 section_iter.advance(); |
| 1050 } | 1088 } |
| 1051 | 1089 |
| 1052 return Vector<const uint8_t>(); | 1090 return Vector<const uint8_t>(); |
| 1053 } | 1091 } |
| 1054 | 1092 |
| 1055 } // namespace | 1093 } // namespace |
| 1056 | 1094 |
| 1057 ModuleResult DecodeWasmModule(Isolate* isolate, Zone* zone, | 1095 ModuleResult DecodeWasmModule(Isolate* isolate, Zone* zone, |
| 1058 const byte* module_start, const byte* module_end, | 1096 const byte* module_start, const byte* module_end, |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1111 // Find and decode the code section. | 1149 // Find and decode the code section. |
| 1112 Vector<const byte> code_section = | 1150 Vector<const byte> code_section = |
| 1113 FindSection(module_start, module_end, kCodeSectionCode); | 1151 FindSection(module_start, module_end, kCodeSectionCode); |
| 1114 Decoder decoder(code_section.start(), code_section.end()); | 1152 Decoder decoder(code_section.start(), code_section.end()); |
| 1115 FunctionOffsets table; | 1153 FunctionOffsets table; |
| 1116 if (!code_section.start()) { | 1154 if (!code_section.start()) { |
| 1117 decoder.error("no code section"); | 1155 decoder.error("no code section"); |
| 1118 return decoder.toResult(std::move(table)); | 1156 return decoder.toResult(std::move(table)); |
| 1119 } | 1157 } |
| 1120 | 1158 |
| 1121 // Reserve entries for the imported functions. | |
| 1122 table.reserve(num_imported_functions); | |
| 1123 for (uint32_t i = 0; i < num_imported_functions; i++) { | |
| 1124 table.push_back(std::make_pair(0, 0)); | |
| 1125 } | |
| 1126 | |
| 1127 uint32_t functions_count = decoder.consume_u32v("functions count"); | 1159 uint32_t functions_count = decoder.consume_u32v("functions count"); |
| 1128 // Take care of invalid input here. | 1160 // Reserve space for the entries, taking care of invalid input. |
| 1129 if (functions_count < static_cast<unsigned>(code_section.length()) / 2) | 1161 if (functions_count < static_cast<unsigned>(code_section.length()) / 2) |
| 1130 table.reserve(num_imported_functions + functions_count); | 1162 table.reserve(num_imported_functions + functions_count); |
| 1163 |
| 1164 // Add null entries for the imported functions. |
| 1165 table.resize(num_imported_functions); |
| 1166 |
| 1131 int section_offset = static_cast<int>(code_section.start() - module_start); | 1167 int section_offset = static_cast<int>(code_section.start() - module_start); |
| 1132 DCHECK_LE(0, section_offset); | 1168 DCHECK_LE(0, section_offset); |
| 1133 for (uint32_t i = 0; i < functions_count && decoder.ok(); ++i) { | 1169 for (uint32_t i = 0; i < functions_count && decoder.ok(); ++i) { |
| 1134 uint32_t size = decoder.consume_u32v("body size"); | 1170 uint32_t size = decoder.consume_u32v("body size"); |
| 1135 int offset = static_cast<int>(section_offset + decoder.pc_offset()); | 1171 int offset = static_cast<int>(section_offset + decoder.pc_offset()); |
| 1136 table.push_back(std::make_pair(offset, static_cast<int>(size))); | 1172 table.push_back(std::make_pair(offset, static_cast<int>(size))); |
| 1137 DCHECK(table.back().first >= 0 && table.back().second >= 0); | 1173 DCHECK(table.back().first >= 0 && table.back().second >= 0); |
| 1138 decoder.consume_bytes(size); | 1174 decoder.consume_bytes(size); |
| 1139 } | 1175 } |
| 1140 if (decoder.more()) decoder.error("unexpected additional bytes"); | 1176 if (decoder.more()) decoder.error("unexpected additional bytes"); |
| 1141 | 1177 |
| 1142 return decoder.toResult(std::move(table)); | 1178 return decoder.toResult(std::move(table)); |
| 1143 } | 1179 } |
| 1144 | 1180 |
| 1181 AsmJsOffsetsResult DecodeAsmJsOffsets(const byte* module_start, |
| 1182 const byte* module_end, |
| 1183 uint32_t num_imported_functions) { |
| 1184 // Find and decode the code section. |
| 1185 Vector<const byte> asm_offset_section = |
| 1186 FindSection(module_start, module_end, kAsmOffsetsSectionCode); |
| 1187 Decoder decoder(asm_offset_section.start(), asm_offset_section.end()); |
| 1188 AsmJsOffsets table; |
| 1189 if (!asm_offset_section.start()) { |
| 1190 decoder.error("no asm.js offsets section"); |
| 1191 return decoder.toResult(std::move(table)); |
| 1192 } |
| 1193 |
| 1194 uint32_t functions_count = decoder.consume_u32v("functions count"); |
| 1195 // Reserve space for the entries, taking care of invalid input. |
| 1196 if (functions_count < static_cast<unsigned>(asm_offset_section.length())) |
| 1197 table.reserve(num_imported_functions + functions_count); |
| 1198 |
| 1199 // Add null entries for the imported functions. |
| 1200 table.resize(num_imported_functions); |
| 1201 |
| 1202 for (uint32_t i = 0; i < functions_count && decoder.ok(); ++i) { |
| 1203 uint32_t size = decoder.consume_u32v("table size"); |
| 1204 if (size == 0) { |
| 1205 table.push_back(std::vector<std::pair<int, int>>()); |
| 1206 continue; |
| 1207 } |
| 1208 if (!decoder.checkAvailable(size)) { |
| 1209 decoder.error("illegal asm function offset table size"); |
| 1210 } |
| 1211 const byte* table_end = decoder.pc() + size; |
| 1212 uint32_t locals_size = decoder.consume_u32("locals size"); |
| 1213 int last_byte_offset = locals_size; |
| 1214 int last_asm_position = 0; |
| 1215 std::vector<std::pair<int, int>> func_asm_offsets; |
| 1216 func_asm_offsets.reserve(size / 4); // conservative estimation |
| 1217 while (decoder.ok() && decoder.pc() < table_end) { |
| 1218 last_byte_offset += decoder.consume_u32v("byte offset delta"); |
| 1219 last_asm_position += decoder.consume_i32v("asm position delta"); |
| 1220 func_asm_offsets.push_back({last_byte_offset, last_asm_position}); |
| 1221 } |
| 1222 if (decoder.pc() != table_end) { |
| 1223 decoder.error("broken asm offset table"); |
| 1224 } |
| 1225 table.push_back(std::move(func_asm_offsets)); |
| 1226 } |
| 1227 if (decoder.more()) decoder.error("unexpected additional bytes"); |
| 1228 |
| 1229 return decoder.toResult(std::move(table)); |
| 1230 } |
| 1231 |
| 1145 } // namespace wasm | 1232 } // namespace wasm |
| 1146 } // namespace internal | 1233 } // namespace internal |
| 1147 } // namespace v8 | 1234 } // namespace v8 |
| OLD | NEW |