| OLD | NEW |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Redistribution and use in source and binary forms, with or without | 2 // Redistribution and use in source and binary forms, with or without |
| 3 // modification, are permitted provided that the following conditions are | 3 // modification, are permitted provided that the following conditions are |
| 4 // met: | 4 // met: |
| 5 // | 5 // |
| 6 // * Redistributions of source code must retain the above copyright | 6 // * Redistributions of source code must retain the above copyright |
| 7 // notice, this list of conditions and the following disclaimer. | 7 // notice, this list of conditions and the following disclaimer. |
| 8 // * Redistributions in binary form must reproduce the above | 8 // * Redistributions in binary form must reproduce the above |
| 9 // copyright notice, this list of conditions and the following | 9 // copyright notice, this list of conditions and the following |
| 10 // disclaimer in the documentation and/or other materials provided | 10 // disclaimer in the documentation and/or other materials provided |
| (...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 result = scanner()->AllocateInternalizedString(isolate_); | 217 result = scanner()->AllocateInternalizedString(isolate_); |
| 218 ASSERT(!result.is_null()); | 218 ASSERT(!result.is_null()); |
| 219 symbol_cache_.at(symbol_id) = result; | 219 symbol_cache_.at(symbol_id) = result; |
| 220 return result; | 220 return result; |
| 221 } | 221 } |
| 222 isolate()->counters()->total_preparse_symbols_skipped()->Increment(); | 222 isolate()->counters()->total_preparse_symbols_skipped()->Increment(); |
| 223 return result; | 223 return result; |
| 224 } | 224 } |
| 225 | 225 |
| 226 | 226 |
| 227 FunctionEntry ScriptDataImpl::GetFunctionEntry(int start) { | 227 ScriptData* ScriptData::New(const char* data, int length) { |
| 228 // The length is obviously invalid. |
| 229 if (length % sizeof(unsigned) != 0) { |
| 230 return new ScriptData(); |
| 231 } |
| 232 |
| 233 int deserialized_data_length = length / sizeof(unsigned); |
| 234 unsigned* deserialized_data; |
| 235 ScriptData* script_data = new ScriptData(); |
| 236 script_data->owns_store_ = |
| 237 reinterpret_cast<intptr_t>(data) % sizeof(unsigned) != 0; |
| 238 if (script_data->owns_store_) { |
| 239 // Copy the data to align it. |
| 240 deserialized_data = i::NewArray<unsigned>(deserialized_data_length); |
| 241 i::CopyBytes(reinterpret_cast<char*>(deserialized_data), |
| 242 data, static_cast<size_t>(length)); |
| 243 } else { |
| 244 // If aligned, don't create a copy of the data. |
| 245 deserialized_data = reinterpret_cast<unsigned*>(const_cast<char*>(data)); |
| 246 } |
| 247 script_data->store_ = |
| 248 Vector<unsigned>(deserialized_data, deserialized_data_length); |
| 249 return script_data; |
| 250 } |
| 251 |
| 252 |
| 253 FunctionEntry ScriptData::GetFunctionEntry(int start) { |
| 228 // The current pre-data entry must be a FunctionEntry with the given | 254 // The current pre-data entry must be a FunctionEntry with the given |
| 229 // start position. | 255 // start position. |
| 230 if ((function_index_ + FunctionEntry::kSize <= store_.length()) | 256 if ((function_index_ + FunctionEntry::kSize <= store_.length()) |
| 231 && (static_cast<int>(store_[function_index_]) == start)) { | 257 && (static_cast<int>(store_[function_index_]) == start)) { |
| 232 int index = function_index_; | 258 int index = function_index_; |
| 233 function_index_ += FunctionEntry::kSize; | 259 function_index_ += FunctionEntry::kSize; |
| 234 return FunctionEntry(store_.SubVector(index, | 260 return FunctionEntry(store_.SubVector(index, |
| 235 index + FunctionEntry::kSize)); | 261 index + FunctionEntry::kSize)); |
| 236 } | 262 } |
| 237 return FunctionEntry(); | 263 return FunctionEntry(); |
| 238 } | 264 } |
| 239 | 265 |
| 240 | 266 |
| 241 int ScriptDataImpl::GetSymbolIdentifier() { | 267 int ScriptData::GetSymbolIdentifier() { |
| 242 return ReadNumber(&symbol_data_); | 268 return ReadNumber(&symbol_data_); |
| 243 } | 269 } |
| 244 | 270 |
| 245 | 271 |
| 246 bool ScriptDataImpl::SanityCheck() { | 272 bool ScriptData::SanityCheck() { |
| 247 // Check that the header data is valid and doesn't specify | 273 // Check that the header data is valid and doesn't specify |
| 248 // point to positions outside the store. | 274 // point to positions outside the store. |
| 249 if (store_.length() < PreparseDataConstants::kHeaderSize) return false; | 275 if (store_.length() < PreparseDataConstants::kHeaderSize) return false; |
| 250 if (magic() != PreparseDataConstants::kMagicNumber) return false; | 276 if (magic() != PreparseDataConstants::kMagicNumber) return false; |
| 251 if (version() != PreparseDataConstants::kCurrentVersion) return false; | 277 if (version() != PreparseDataConstants::kCurrentVersion) return false; |
| 252 if (has_error()) { | 278 if (has_error()) { |
| 253 // Extra sane sanity check for error message encoding. | 279 // Extra sane sanity check for error message encoding. |
| 254 if (store_.length() <= PreparseDataConstants::kHeaderSize | 280 if (store_.length() <= PreparseDataConstants::kHeaderSize |
| 255 + PreparseDataConstants::kMessageTextPos) { | 281 + PreparseDataConstants::kMessageTextPos) { |
| 256 return false; | 282 return false; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 285 if (symbol_count < 0) return false; | 311 if (symbol_count < 0) return false; |
| 286 // Check that the total size has room for header and function entries. | 312 // Check that the total size has room for header and function entries. |
| 287 int minimum_size = | 313 int minimum_size = |
| 288 PreparseDataConstants::kHeaderSize + functions_size; | 314 PreparseDataConstants::kHeaderSize + functions_size; |
| 289 if (store_.length() < minimum_size) return false; | 315 if (store_.length() < minimum_size) return false; |
| 290 return true; | 316 return true; |
| 291 } | 317 } |
| 292 | 318 |
| 293 | 319 |
| 294 | 320 |
| 295 const char* ScriptDataImpl::ReadString(unsigned* start, int* chars) { | 321 const char* ScriptData::ReadString(unsigned* start, int* chars) { |
| 296 int length = start[0]; | 322 int length = start[0]; |
| 297 char* result = NewArray<char>(length + 1); | 323 char* result = NewArray<char>(length + 1); |
| 298 for (int i = 0; i < length; i++) { | 324 for (int i = 0; i < length; i++) { |
| 299 result[i] = start[i + 1]; | 325 result[i] = start[i + 1]; |
| 300 } | 326 } |
| 301 result[length] = '\0'; | 327 result[length] = '\0'; |
| 302 if (chars != NULL) *chars = length; | 328 if (chars != NULL) *chars = length; |
| 303 return result; | 329 return result; |
| 304 } | 330 } |
| 305 | 331 |
| 306 | 332 |
| 307 Scanner::Location ScriptDataImpl::MessageLocation() const { | 333 Scanner::Location ScriptData::MessageLocation() const { |
| 308 int beg_pos = Read(PreparseDataConstants::kMessageStartPos); | 334 int beg_pos = Read(PreparseDataConstants::kMessageStartPos); |
| 309 int end_pos = Read(PreparseDataConstants::kMessageEndPos); | 335 int end_pos = Read(PreparseDataConstants::kMessageEndPos); |
| 310 return Scanner::Location(beg_pos, end_pos); | 336 return Scanner::Location(beg_pos, end_pos); |
| 311 } | 337 } |
| 312 | 338 |
| 313 | 339 |
| 314 bool ScriptDataImpl::IsReferenceError() const { | 340 bool ScriptData::IsReferenceError() const { |
| 315 return Read(PreparseDataConstants::kIsReferenceErrorPos); | 341 return Read(PreparseDataConstants::kIsReferenceErrorPos); |
| 316 } | 342 } |
| 317 | 343 |
| 318 | 344 |
| 319 const char* ScriptDataImpl::BuildMessage() const { | 345 const char* ScriptData::BuildMessage() const { |
| 320 unsigned* start = ReadAddress(PreparseDataConstants::kMessageTextPos); | 346 unsigned* start = ReadAddress(PreparseDataConstants::kMessageTextPos); |
| 321 return ReadString(start, NULL); | 347 return ReadString(start, NULL); |
| 322 } | 348 } |
| 323 | 349 |
| 324 | 350 |
| 325 Vector<const char*> ScriptDataImpl::BuildArgs() const { | 351 Vector<const char*> ScriptData::BuildArgs() const { |
| 326 int arg_count = Read(PreparseDataConstants::kMessageArgCountPos); | 352 int arg_count = Read(PreparseDataConstants::kMessageArgCountPos); |
| 327 const char** array = NewArray<const char*>(arg_count); | 353 const char** array = NewArray<const char*>(arg_count); |
| 328 // Position after text found by skipping past length field and | 354 // Position after text found by skipping past length field and |
| 329 // length field content words. | 355 // length field content words. |
| 330 int pos = PreparseDataConstants::kMessageTextPos + 1 | 356 int pos = PreparseDataConstants::kMessageTextPos + 1 |
| 331 + Read(PreparseDataConstants::kMessageTextPos); | 357 + Read(PreparseDataConstants::kMessageTextPos); |
| 332 for (int i = 0; i < arg_count; i++) { | 358 for (int i = 0; i < arg_count; i++) { |
| 333 int count = 0; | 359 int count = 0; |
| 334 array[i] = ReadString(ReadAddress(pos), &count); | 360 array[i] = ReadString(ReadAddress(pos), &count); |
| 335 pos += count + 1; | 361 pos += count + 1; |
| 336 } | 362 } |
| 337 return Vector<const char*>(array, arg_count); | 363 return Vector<const char*>(array, arg_count); |
| 338 } | 364 } |
| 339 | 365 |
| 340 | 366 |
| 341 unsigned ScriptDataImpl::Read(int position) const { | 367 unsigned ScriptData::Read(int position) const { |
| 342 return store_[PreparseDataConstants::kHeaderSize + position]; | 368 return store_[PreparseDataConstants::kHeaderSize + position]; |
| 343 } | 369 } |
| 344 | 370 |
| 345 | 371 |
| 346 unsigned* ScriptDataImpl::ReadAddress(int position) const { | 372 unsigned* ScriptData::ReadAddress(int position) const { |
| 347 return &store_[PreparseDataConstants::kHeaderSize + position]; | 373 return &store_[PreparseDataConstants::kHeaderSize + position]; |
| 348 } | 374 } |
| 349 | 375 |
| 350 | 376 |
| 351 Scope* Parser::NewScope(Scope* parent, ScopeType scope_type) { | 377 Scope* Parser::NewScope(Scope* parent, ScopeType scope_type) { |
| 352 Scope* result = new(zone()) Scope(parent, scope_type, zone()); | 378 Scope* result = new(zone()) Scope(parent, scope_type, zone()); |
| 353 result->Initialize(); | 379 result->Initialize(); |
| 354 return result; | 380 return result; |
| 355 } | 381 } |
| 356 | 382 |
| (...skipping 526 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 883 String* name = String::cast(info()->script()->name()); | 909 String* name = String::cast(info()->script()->name()); |
| 884 SmartArrayPointer<char> name_chars = name->ToCString(); | 910 SmartArrayPointer<char> name_chars = name->ToCString(); |
| 885 PrintF("[parsing script: %s", name_chars.get()); | 911 PrintF("[parsing script: %s", name_chars.get()); |
| 886 } else { | 912 } else { |
| 887 PrintF("[parsing script"); | 913 PrintF("[parsing script"); |
| 888 } | 914 } |
| 889 PrintF(" - took %0.3f ms]\n", ms); | 915 PrintF(" - took %0.3f ms]\n", ms); |
| 890 } | 916 } |
| 891 if (cached_data_mode_ == PRODUCE_CACHED_DATA) { | 917 if (cached_data_mode_ == PRODUCE_CACHED_DATA) { |
| 892 Vector<unsigned> store = recorder.ExtractData(); | 918 Vector<unsigned> store = recorder.ExtractData(); |
| 893 *cached_data_ = new ScriptDataImpl(store); | 919 *cached_data_ = new ScriptData(store); |
| 894 log_ = NULL; | 920 log_ = NULL; |
| 895 } | 921 } |
| 896 return result; | 922 return result; |
| 897 } | 923 } |
| 898 | 924 |
| 899 | 925 |
| 900 FunctionLiteral* Parser::DoParseProgram(CompilationInfo* info, | 926 FunctionLiteral* Parser::DoParseProgram(CompilationInfo* info, |
| 901 Handle<String> source) { | 927 Handle<String> source) { |
| 902 ASSERT(scope_ == NULL); | 928 ASSERT(scope_ == NULL); |
| 903 ASSERT(target_stack_ == NULL); | 929 ASSERT(target_stack_ == NULL); |
| (...skipping 3626 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4530 ranges->Add(CharacterRange::Everything(), zone()); | 4556 ranges->Add(CharacterRange::Everything(), zone()); |
| 4531 is_negated = !is_negated; | 4557 is_negated = !is_negated; |
| 4532 } | 4558 } |
| 4533 return new(zone()) RegExpCharacterClass(ranges, is_negated); | 4559 return new(zone()) RegExpCharacterClass(ranges, is_negated); |
| 4534 } | 4560 } |
| 4535 | 4561 |
| 4536 | 4562 |
| 4537 // ---------------------------------------------------------------------------- | 4563 // ---------------------------------------------------------------------------- |
| 4538 // The Parser interface. | 4564 // The Parser interface. |
| 4539 | 4565 |
| 4540 ScriptDataImpl::~ScriptDataImpl() { | 4566 ScriptData::~ScriptData() { |
| 4541 if (owns_store_) store_.Dispose(); | 4567 if (owns_store_) store_.Dispose(); |
| 4542 } | 4568 } |
| 4543 | 4569 |
| 4544 | 4570 |
| 4545 int ScriptDataImpl::Length() { | 4571 int ScriptData::Length() { |
| 4546 return store_.length() * sizeof(unsigned); | 4572 return store_.length() * sizeof(unsigned); |
| 4547 } | 4573 } |
| 4548 | 4574 |
| 4549 | 4575 |
| 4550 const char* ScriptDataImpl::Data() { | 4576 const char* ScriptData::Data() { |
| 4551 return reinterpret_cast<const char*>(store_.start()); | 4577 return reinterpret_cast<const char*>(store_.start()); |
| 4552 } | 4578 } |
| 4553 | 4579 |
| 4554 | 4580 |
| 4555 bool ScriptDataImpl::HasError() { | 4581 bool ScriptData::HasError() { |
| 4556 return has_error(); | 4582 return has_error(); |
| 4557 } | 4583 } |
| 4558 | 4584 |
| 4559 | 4585 |
| 4560 void ScriptDataImpl::Initialize() { | 4586 void ScriptData::Initialize() { |
| 4561 // Prepares state for use. | 4587 // Prepares state for use. |
| 4562 if (store_.length() >= PreparseDataConstants::kHeaderSize) { | 4588 if (store_.length() >= PreparseDataConstants::kHeaderSize) { |
| 4563 function_index_ = PreparseDataConstants::kHeaderSize; | 4589 function_index_ = PreparseDataConstants::kHeaderSize; |
| 4564 int symbol_data_offset = PreparseDataConstants::kHeaderSize | 4590 int symbol_data_offset = PreparseDataConstants::kHeaderSize |
| 4565 + store_[PreparseDataConstants::kFunctionsSizeOffset]; | 4591 + store_[PreparseDataConstants::kFunctionsSizeOffset]; |
| 4566 if (store_.length() > symbol_data_offset) { | 4592 if (store_.length() > symbol_data_offset) { |
| 4567 symbol_data_ = reinterpret_cast<byte*>(&store_[symbol_data_offset]); | 4593 symbol_data_ = reinterpret_cast<byte*>(&store_[symbol_data_offset]); |
| 4568 } else { | 4594 } else { |
| 4569 // Partial preparse causes no symbol information. | 4595 // Partial preparse causes no symbol information. |
| 4570 symbol_data_ = reinterpret_cast<byte*>(&store_[0] + store_.length()); | 4596 symbol_data_ = reinterpret_cast<byte*>(&store_[0] + store_.length()); |
| 4571 } | 4597 } |
| 4572 symbol_data_end_ = reinterpret_cast<byte*>(&store_[0] + store_.length()); | 4598 symbol_data_end_ = reinterpret_cast<byte*>(&store_[0] + store_.length()); |
| 4573 } | 4599 } |
| 4574 } | 4600 } |
| 4575 | 4601 |
| 4576 | 4602 |
| 4577 int ScriptDataImpl::ReadNumber(byte** source) { | 4603 int ScriptData::ReadNumber(byte** source) { |
| 4578 // Reads a number from symbol_data_ in base 128. The most significant | 4604 // Reads a number from symbol_data_ in base 128. The most significant |
| 4579 // bit marks that there are more digits. | 4605 // bit marks that there are more digits. |
| 4580 // If the first byte is 0x80 (kNumberTerminator), it would normally | 4606 // If the first byte is 0x80 (kNumberTerminator), it would normally |
| 4581 // represent a leading zero. Since that is useless, and therefore won't | 4607 // represent a leading zero. Since that is useless, and therefore won't |
| 4582 // appear as the first digit of any actual value, it is used to | 4608 // appear as the first digit of any actual value, it is used to |
| 4583 // mark the end of the input stream. | 4609 // mark the end of the input stream. |
| 4584 byte* data = *source; | 4610 byte* data = *source; |
| 4585 if (data >= symbol_data_end_) return -1; | 4611 if (data >= symbol_data_end_) return -1; |
| 4586 byte input = *data; | 4612 byte input = *data; |
| 4587 if (input == PreparseDataConstants::kNumberTerminator) { | 4613 if (input == PreparseDataConstants::kNumberTerminator) { |
| 4588 // End of stream marker. | 4614 // End of stream marker. |
| 4589 return -1; | 4615 return -1; |
| 4590 } | 4616 } |
| 4591 int result = input & 0x7f; | 4617 int result = input & 0x7f; |
| 4592 data++; | 4618 data++; |
| 4593 while ((input & 0x80u) != 0) { | 4619 while ((input & 0x80u) != 0) { |
| 4594 if (data >= symbol_data_end_) return -1; | 4620 if (data >= symbol_data_end_) return -1; |
| 4595 input = *data; | 4621 input = *data; |
| 4596 result = (result << 7) | (input & 0x7f); | 4622 result = (result << 7) | (input & 0x7f); |
| 4597 data++; | 4623 data++; |
| 4598 } | 4624 } |
| 4599 *source = data; | 4625 *source = data; |
| 4600 return result; | 4626 return result; |
| 4601 } | 4627 } |
| 4602 | 4628 |
| 4603 | 4629 |
| 4604 // Create a Scanner for the preparser to use as input, and preparse the source. | |
| 4605 ScriptDataImpl* PreParserApi::PreParse(Isolate* isolate, | |
| 4606 Utf16CharacterStream* source) { | |
| 4607 CompleteParserRecorder recorder; | |
| 4608 HistogramTimerScope timer(isolate->counters()->pre_parse()); | |
| 4609 Scanner scanner(isolate->unicode_cache()); | |
| 4610 intptr_t stack_limit = isolate->stack_guard()->real_climit(); | |
| 4611 PreParser preparser(&scanner, &recorder, stack_limit); | |
| 4612 preparser.set_allow_lazy(true); | |
| 4613 preparser.set_allow_generators(FLAG_harmony_generators); | |
| 4614 preparser.set_allow_for_of(FLAG_harmony_iteration); | |
| 4615 preparser.set_allow_harmony_scoping(FLAG_harmony_scoping); | |
| 4616 preparser.set_allow_harmony_numeric_literals(FLAG_harmony_numeric_literals); | |
| 4617 scanner.Initialize(source); | |
| 4618 PreParser::PreParseResult result = preparser.PreParseProgram(); | |
| 4619 if (result == PreParser::kPreParseStackOverflow) { | |
| 4620 isolate->StackOverflow(); | |
| 4621 return NULL; | |
| 4622 } | |
| 4623 | |
| 4624 // Extract the accumulated data from the recorder as a single | |
| 4625 // contiguous vector that we are responsible for disposing. | |
| 4626 Vector<unsigned> store = recorder.ExtractData(); | |
| 4627 return new ScriptDataImpl(store); | |
| 4628 } | |
| 4629 | |
| 4630 | |
| 4631 bool RegExpParser::ParseRegExp(FlatStringReader* input, | 4630 bool RegExpParser::ParseRegExp(FlatStringReader* input, |
| 4632 bool multiline, | 4631 bool multiline, |
| 4633 RegExpCompileData* result, | 4632 RegExpCompileData* result, |
| 4634 Zone* zone) { | 4633 Zone* zone) { |
| 4635 ASSERT(result != NULL); | 4634 ASSERT(result != NULL); |
| 4636 RegExpParser parser(input, &result->error, multiline, zone); | 4635 RegExpParser parser(input, &result->error, multiline, zone); |
| 4637 RegExpTree* tree = parser.ParsePattern(); | 4636 RegExpTree* tree = parser.ParsePattern(); |
| 4638 if (parser.failed()) { | 4637 if (parser.failed()) { |
| 4639 ASSERT(tree == NULL); | 4638 ASSERT(tree == NULL); |
| 4640 ASSERT(!result->error.is_null()); | 4639 ASSERT(!result->error.is_null()); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 4658 ASSERT(!info()->is_eval()); | 4657 ASSERT(!info()->is_eval()); |
| 4659 if (info()->shared_info()->is_function()) { | 4658 if (info()->shared_info()->is_function()) { |
| 4660 result = ParseLazy(); | 4659 result = ParseLazy(); |
| 4661 } else { | 4660 } else { |
| 4662 result = ParseProgram(); | 4661 result = ParseProgram(); |
| 4663 } | 4662 } |
| 4664 } else { | 4663 } else { |
| 4665 SetCachedData(info()->cached_data(), info()->cached_data_mode()); | 4664 SetCachedData(info()->cached_data(), info()->cached_data_mode()); |
| 4666 if (info()->cached_data_mode() == CONSUME_CACHED_DATA && | 4665 if (info()->cached_data_mode() == CONSUME_CACHED_DATA && |
| 4667 (*info()->cached_data())->has_error()) { | 4666 (*info()->cached_data())->has_error()) { |
| 4668 ScriptDataImpl* cached_data = *(info()->cached_data()); | 4667 ScriptData* cached_data = *(info()->cached_data()); |
| 4669 Scanner::Location loc = cached_data->MessageLocation(); | 4668 Scanner::Location loc = cached_data->MessageLocation(); |
| 4670 const char* message = cached_data->BuildMessage(); | 4669 const char* message = cached_data->BuildMessage(); |
| 4671 Vector<const char*> args = cached_data->BuildArgs(); | 4670 Vector<const char*> args = cached_data->BuildArgs(); |
| 4672 ParserTraits::ReportMessageAt(loc, message, args, | 4671 ParserTraits::ReportMessageAt(loc, message, args, |
| 4673 cached_data->IsReferenceError()); | 4672 cached_data->IsReferenceError()); |
| 4674 DeleteArray(message); | 4673 DeleteArray(message); |
| 4675 for (int i = 0; i < args.length(); i++) { | 4674 for (int i = 0; i < args.length(); i++) { |
| 4676 DeleteArray(args[i]); | 4675 DeleteArray(args[i]); |
| 4677 } | 4676 } |
| 4678 DeleteArray(args.start()); | 4677 DeleteArray(args.start()); |
| 4679 ASSERT(info()->isolate()->has_pending_exception()); | 4678 ASSERT(info()->isolate()->has_pending_exception()); |
| 4680 } else { | 4679 } else { |
| 4681 result = ParseProgram(); | 4680 result = ParseProgram(); |
| 4682 } | 4681 } |
| 4683 } | 4682 } |
| 4684 info()->SetFunction(result); | 4683 info()->SetFunction(result); |
| 4685 return (result != NULL); | 4684 return (result != NULL); |
| 4686 } | 4685 } |
| 4687 | 4686 |
| 4688 } } // namespace v8::internal | 4687 } } // namespace v8::internal |
| OLD | NEW |