1// Copyright 2012 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/debug/liveedit.h" 6 7#include "src/api/api-inl.h" 8#include "src/ast/ast-traversal-visitor.h" 9#include "src/ast/ast.h" 10#include "src/ast/scopes.h" 11#include "src/codegen/compilation-cache.h" 12#include "src/codegen/compiler.h" 13#include "src/codegen/source-position-table.h" 14#include "src/common/globals.h" 15#include "src/debug/debug-interface.h" 16#include "src/debug/debug.h" 17#include "src/execution/frames-inl.h" 18#include "src/execution/isolate-inl.h" 19#include "src/execution/v8threads.h" 20#include "src/init/v8.h" 21#include "src/logging/log.h" 22#include "src/objects/hash-table-inl.h" 23#include "src/objects/js-generator-inl.h" 24#include "src/objects/objects-inl.h" 25#include "src/parsing/parse-info.h" 26#include "src/parsing/parsing.h" 27 28namespace v8 { 29namespace internal { 30namespace { 31// A general-purpose comparator between 2 arrays. 32class Comparator { 33 public: 34 // Holds 2 arrays of some elements allowing to compare any pair of 35 // element from the first array and element from the second array. 36 class Input { 37 public: 38 virtual int GetLength1() = 0; 39 virtual int GetLength2() = 0; 40 virtual bool Equals(int index1, int index2) = 0; 41 42 protected: 43 virtual ~Input() = default; 44 }; 45 46 // Receives compare result as a series of chunks. 47 class Output { 48 public: 49 // Puts another chunk in result list. Note that technically speaking 50 // only 3 arguments actually needed with 4th being derivable. 51 virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0; 52 53 protected: 54 virtual ~Output() = default; 55 }; 56 57 // Finds the difference between 2 arrays of elements. 58 static void CalculateDifference(Input* input, Output* result_writer); 59}; 60 61// A simple implementation of dynamic programming algorithm. It solves 62// the problem of finding the difference of 2 arrays. It uses a table of results 63// of subproblems. Each cell contains a number together with 2-bit flag 64// that helps building the chunk list. 65class Differencer { 66 public: 67 explicit Differencer(Comparator::Input* input) 68 : input_(input), len1_(input->GetLength1()), len2_(input->GetLength2()) { 69 } 70 71 void Initialize() { 72 } 73 74 // Makes sure that result for the full problem is calculated and stored 75 // in the table together with flags showing a path through subproblems. 76 void FillTable() { 77 // Determine common prefix to skip. 78 int minLen = std::min(len1_, len2_); 79 while (prefixLen_ < minLen && input_->Equals(prefixLen_, prefixLen_)) { 80 ++prefixLen_; 81 } 82 83 // Pre-fill common suffix in the table. 84 for (int pos1 = len1_, pos2 = len2_; pos1 > prefixLen_ && 85 pos2 > prefixLen_ && 86 input_->Equals(--pos1, --pos2);) { 87 set_value4_and_dir(pos1, pos2, 0, EQ); 88 } 89 90 CompareUpToTail(prefixLen_, prefixLen_); 91 } 92 93 void SaveResult(Comparator::Output* chunk_writer) { 94 ResultWriter writer(chunk_writer); 95 96 if (prefixLen_) writer.eq(prefixLen_); 97 for (int pos1 = prefixLen_, pos2 = prefixLen_; true;) { 98 if (pos1 < len1_) { 99 if (pos2 < len2_) { 100 Direction dir = get_direction(pos1, pos2); 101 switch (dir) { 102 case EQ: 103 writer.eq(); 104 pos1++; 105 pos2++; 106 break; 107 case SKIP1: 108 writer.skip1(1); 109 pos1++; 110 break; 111 case SKIP2: 112 case SKIP_ANY: 113 writer.skip2(1); 114 pos2++; 115 break; 116 default: 117 UNREACHABLE(); 118 } 119 } else { 120 writer.skip1(len1_ - pos1); 121 break; 122 } 123 } else { 124 if (len2_ != pos2) { 125 writer.skip2(len2_ - pos2); 126 } 127 break; 128 } 129 } 130 writer.close(); 131 } 132 133 private: 134 Comparator::Input* input_; 135 std::map<std::pair<int, int>, int> buffer_; 136 int len1_; 137 int len2_; 138 int prefixLen_ = 0; 139 140 enum Direction { 141 EQ = 0, 142 SKIP1, 143 SKIP2, 144 SKIP_ANY, 145 146 MAX_DIRECTION_FLAG_VALUE = SKIP_ANY 147 }; 148 149 // Computes result for a subtask and optionally caches it in the buffer table. 150 // All results values are shifted to make space for flags in the lower bits. 151 int CompareUpToTail(int pos1, int pos2) { 152 if (pos1 == len1_) { 153 return (len2_ - pos2) << kDirectionSizeBits; 154 } 155 if (pos2 == len2_) { 156 return (len1_ - pos1) << kDirectionSizeBits; 157 } 158 int res = get_value4(pos1, pos2); 159 if (res != kEmptyCellValue) { 160 return res; 161 } 162 Direction dir; 163 if (input_->Equals(pos1, pos2)) { 164 res = CompareUpToTail(pos1 + 1, pos2 + 1); 165 dir = EQ; 166 } else { 167 int res1 = CompareUpToTail(pos1 + 1, pos2) + (1 << kDirectionSizeBits); 168 int res2 = CompareUpToTail(pos1, pos2 + 1) + (1 << kDirectionSizeBits); 169 if (res1 == res2) { 170 res = res1; 171 dir = SKIP_ANY; 172 } else if (res1 < res2) { 173 res = res1; 174 dir = SKIP1; 175 } else { 176 res = res2; 177 dir = SKIP2; 178 } 179 } 180 set_value4_and_dir(pos1, pos2, res, dir); 181 return res; 182 } 183 184 inline int get_cell(int i1, int i2) { 185 auto it = buffer_.find(std::make_pair(i1, i2)); 186 return it == buffer_.end() ? kEmptyCellValue : it->second; 187 } 188 189 inline void set_cell(int i1, int i2, int value) { 190 buffer_.insert(std::make_pair(std::make_pair(i1, i2), value)); 191 } 192 193 // Each cell keeps a value plus direction. Value is multiplied by 4. 194 void set_value4_and_dir(int i1, int i2, int value4, Direction dir) { 195 DCHECK_EQ(0, value4 & kDirectionMask); 196 set_cell(i1, i2, value4 | dir); 197 } 198 199 int get_value4(int i1, int i2) { 200 return get_cell(i1, i2) & (kMaxUInt32 ^ kDirectionMask); 201 } 202 Direction get_direction(int i1, int i2) { 203 return static_cast<Direction>(get_cell(i1, i2) & kDirectionMask); 204 } 205 206 static const int kDirectionSizeBits = 2; 207 static const int kDirectionMask = (1 << kDirectionSizeBits) - 1; 208 static const int kEmptyCellValue = ~0u << kDirectionSizeBits; 209 210 // This method only holds static assert statement (unfortunately you cannot 211 // place one in class scope). 212 void StaticAssertHolder() { 213 STATIC_ASSERT(MAX_DIRECTION_FLAG_VALUE < (1 << kDirectionSizeBits)); 214 } 215 216 class ResultWriter { 217 public: 218 explicit ResultWriter(Comparator::Output* chunk_writer) 219 : chunk_writer_(chunk_writer), pos1_(0), pos2_(0), 220 pos1_begin_(-1), pos2_begin_(-1), has_open_chunk_(false) { 221 } 222 void eq(int len = 1) { 223 FlushChunk(); 224 pos1_ += len; 225 pos2_ += len; 226 } 227 void skip1(int len1) { 228 StartChunk(); 229 pos1_ += len1; 230 } 231 void skip2(int len2) { 232 StartChunk(); 233 pos2_ += len2; 234 } 235 void close() { 236 FlushChunk(); 237 } 238 239 private: 240 Comparator::Output* chunk_writer_; 241 int pos1_; 242 int pos2_; 243 int pos1_begin_; 244 int pos2_begin_; 245 bool has_open_chunk_; 246 247 void StartChunk() { 248 if (!has_open_chunk_) { 249 pos1_begin_ = pos1_; 250 pos2_begin_ = pos2_; 251 has_open_chunk_ = true; 252 } 253 } 254 255 void FlushChunk() { 256 if (has_open_chunk_) { 257 chunk_writer_->AddChunk(pos1_begin_, pos2_begin_, 258 pos1_ - pos1_begin_, pos2_ - pos2_begin_); 259 has_open_chunk_ = false; 260 } 261 } 262 }; 263}; 264 265void Comparator::CalculateDifference(Comparator::Input* input, 266 Comparator::Output* result_writer) { 267 Differencer differencer(input); 268 differencer.Initialize(); 269 differencer.FillTable(); 270 differencer.SaveResult(result_writer); 271} 272 273bool CompareSubstrings(Handle<String> s1, int pos1, Handle<String> s2, int pos2, 274 int len) { 275 for (int i = 0; i < len; i++) { 276 if (s1->Get(i + pos1) != s2->Get(i + pos2)) return false; 277 } 278 return true; 279} 280 281// Additional to Input interface. Lets switch Input range to subrange. 282// More elegant way would be to wrap one Input as another Input object 283// and translate positions there, but that would cost us additional virtual 284// call per comparison. 285class SubrangableInput : public Comparator::Input { 286 public: 287 virtual void SetSubrange1(int offset, int len) = 0; 288 virtual void SetSubrange2(int offset, int len) = 0; 289}; 290 291 292class SubrangableOutput : public Comparator::Output { 293 public: 294 virtual void SetSubrange1(int offset, int len) = 0; 295 virtual void SetSubrange2(int offset, int len) = 0; 296}; 297 298// Finds common prefix and suffix in input. This parts shouldn't take space in 299// linear programming table. Enable subranging in input and output. 300void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) { 301 const int len1 = input->GetLength1(); 302 const int len2 = input->GetLength2(); 303 304 int common_prefix_len; 305 int common_suffix_len; 306 307 { 308 common_prefix_len = 0; 309 int prefix_limit = std::min(len1, len2); 310 while (common_prefix_len < prefix_limit && 311 input->Equals(common_prefix_len, common_prefix_len)) { 312 common_prefix_len++; 313 } 314 315 common_suffix_len = 0; 316 int suffix_limit = 317 std::min(len1 - common_prefix_len, len2 - common_prefix_len); 318 319 while (common_suffix_len < suffix_limit && 320 input->Equals(len1 - common_suffix_len - 1, 321 len2 - common_suffix_len - 1)) { 322 common_suffix_len++; 323 } 324 } 325 326 if (common_prefix_len > 0 || common_suffix_len > 0) { 327 int new_len1 = len1 - common_suffix_len - common_prefix_len; 328 int new_len2 = len2 - common_suffix_len - common_prefix_len; 329 330 input->SetSubrange1(common_prefix_len, new_len1); 331 input->SetSubrange2(common_prefix_len, new_len2); 332 333 output->SetSubrange1(common_prefix_len, new_len1); 334 output->SetSubrange2(common_prefix_len, new_len2); 335 } 336} 337 338// Represents 2 strings as 2 arrays of tokens. 339// TODO(LiveEdit): Currently it's actually an array of charactres. 340// Make array of tokens instead. 341class TokensCompareInput : public Comparator::Input { 342 public: 343 TokensCompareInput(Handle<String> s1, int offset1, int len1, 344 Handle<String> s2, int offset2, int len2) 345 : s1_(s1), offset1_(offset1), len1_(len1), 346 s2_(s2), offset2_(offset2), len2_(len2) { 347 } 348 int GetLength1() override { return len1_; } 349 int GetLength2() override { return len2_; } 350 bool Equals(int index1, int index2) override { 351 return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2); 352 } 353 354 private: 355 Handle<String> s1_; 356 int offset1_; 357 int len1_; 358 Handle<String> s2_; 359 int offset2_; 360 int len2_; 361}; 362 363// Stores compare result in std::vector. Converts substring positions 364// to absolute positions. 365class TokensCompareOutput : public Comparator::Output { 366 public: 367 TokensCompareOutput(int offset1, int offset2, 368 std::vector<SourceChangeRange>* output) 369 : output_(output), offset1_(offset1), offset2_(offset2) {} 370 371 void AddChunk(int pos1, int pos2, int len1, int len2) override { 372 output_->emplace_back( 373 SourceChangeRange{pos1 + offset1_, pos1 + len1 + offset1_, 374 pos2 + offset2_, pos2 + offset2_ + len2}); 375 } 376 377 private: 378 std::vector<SourceChangeRange>* output_; 379 int offset1_; 380 int offset2_; 381}; 382 383// Wraps raw n-elements line_ends array as a list of n+1 lines. The last line 384// never has terminating new line character. 385class LineEndsWrapper { 386 public: 387 explicit LineEndsWrapper(Isolate* isolate, Handle<String> string) 388 : ends_array_(String::CalculateLineEnds(isolate, string, false)), 389 string_len_(string->length()) {} 390 int length() { 391 return ends_array_->length() + 1; 392 } 393 // Returns start for any line including start of the imaginary line after 394 // the last line. 395 int GetLineStart(int index) { return index == 0 ? 0 : GetLineEnd(index - 1); } 396 int GetLineEnd(int index) { 397 if (index == ends_array_->length()) { 398 // End of the last line is always an end of the whole string. 399 // If the string ends with a new line character, the last line is an 400 // empty string after this character. 401 return string_len_; 402 } else { 403 return GetPosAfterNewLine(index); 404 } 405 } 406 407 private: 408 Handle<FixedArray> ends_array_; 409 int string_len_; 410 411 int GetPosAfterNewLine(int index) { 412 return Smi::ToInt(ends_array_->get(index)) + 1; 413 } 414}; 415 416// Represents 2 strings as 2 arrays of lines. 417class LineArrayCompareInput : public SubrangableInput { 418 public: 419 LineArrayCompareInput(Handle<String> s1, Handle<String> s2, 420 LineEndsWrapper line_ends1, LineEndsWrapper line_ends2) 421 : s1_(s1), s2_(s2), line_ends1_(line_ends1), 422 line_ends2_(line_ends2), 423 subrange_offset1_(0), subrange_offset2_(0), 424 subrange_len1_(line_ends1_.length()), 425 subrange_len2_(line_ends2_.length()) { 426 } 427 int GetLength1() override { return subrange_len1_; } 428 int GetLength2() override { return subrange_len2_; } 429 bool Equals(int index1, int index2) override { 430 index1 += subrange_offset1_; 431 index2 += subrange_offset2_; 432 433 int line_start1 = line_ends1_.GetLineStart(index1); 434 int line_start2 = line_ends2_.GetLineStart(index2); 435 int line_end1 = line_ends1_.GetLineEnd(index1); 436 int line_end2 = line_ends2_.GetLineEnd(index2); 437 int len1 = line_end1 - line_start1; 438 int len2 = line_end2 - line_start2; 439 if (len1 != len2) { 440 return false; 441 } 442 return CompareSubstrings(s1_, line_start1, s2_, line_start2, 443 len1); 444 } 445 void SetSubrange1(int offset, int len) override { 446 subrange_offset1_ = offset; 447 subrange_len1_ = len; 448 } 449 void SetSubrange2(int offset, int len) override { 450 subrange_offset2_ = offset; 451 subrange_len2_ = len; 452 } 453 454 private: 455 Handle<String> s1_; 456 Handle<String> s2_; 457 LineEndsWrapper line_ends1_; 458 LineEndsWrapper line_ends2_; 459 int subrange_offset1_; 460 int subrange_offset2_; 461 int subrange_len1_; 462 int subrange_len2_; 463}; 464 465// Stores compare result in std::vector. For each chunk tries to conduct 466// a fine-grained nested diff token-wise. 467class TokenizingLineArrayCompareOutput : public SubrangableOutput { 468 public: 469 TokenizingLineArrayCompareOutput(Isolate* isolate, LineEndsWrapper line_ends1, 470 LineEndsWrapper line_ends2, 471 Handle<String> s1, Handle<String> s2, 472 std::vector<SourceChangeRange>* output) 473 : isolate_(isolate), 474 line_ends1_(line_ends1), 475 line_ends2_(line_ends2), 476 s1_(s1), 477 s2_(s2), 478 subrange_offset1_(0), 479 subrange_offset2_(0), 480 output_(output) {} 481 482 void AddChunk(int line_pos1, int line_pos2, int line_len1, 483 int line_len2) override { 484 line_pos1 += subrange_offset1_; 485 line_pos2 += subrange_offset2_; 486 487 int char_pos1 = line_ends1_.GetLineStart(line_pos1); 488 int char_pos2 = line_ends2_.GetLineStart(line_pos2); 489 int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1; 490 int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2; 491 492 if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) { 493 // Chunk is small enough to conduct a nested token-level diff. 494 HandleScope subTaskScope(isolate_); 495 496 TokensCompareInput tokens_input(s1_, char_pos1, char_len1, 497 s2_, char_pos2, char_len2); 498 TokensCompareOutput tokens_output(char_pos1, char_pos2, output_); 499 500 Comparator::CalculateDifference(&tokens_input, &tokens_output); 501 } else { 502 output_->emplace_back(SourceChangeRange{ 503 char_pos1, char_pos1 + char_len1, char_pos2, char_pos2 + char_len2}); 504 } 505 } 506 void SetSubrange1(int offset, int len) override { 507 subrange_offset1_ = offset; 508 } 509 void SetSubrange2(int offset, int len) override { 510 subrange_offset2_ = offset; 511 } 512 513 private: 514 static const int CHUNK_LEN_LIMIT = 800; 515 516 Isolate* isolate_; 517 LineEndsWrapper line_ends1_; 518 LineEndsWrapper line_ends2_; 519 Handle<String> s1_; 520 Handle<String> s2_; 521 int subrange_offset1_; 522 int subrange_offset2_; 523 std::vector<SourceChangeRange>* output_; 524}; 525 526struct SourcePositionEvent { 527 enum Type { LITERAL_STARTS, LITERAL_ENDS, DIFF_STARTS, DIFF_ENDS }; 528 529 int position; 530 Type type; 531 532 union { 533 FunctionLiteral* literal; 534 int pos_diff; 535 }; 536 537 SourcePositionEvent(FunctionLiteral* literal, bool is_start) 538 : position(is_start ? literal->start_position() 539 : literal->end_position()), 540 type(is_start ? LITERAL_STARTS : LITERAL_ENDS), 541 literal(literal) {} 542 SourcePositionEvent(const SourceChangeRange& change, bool is_start) 543 : position(is_start ? change.start_position : change.end_position), 544 type(is_start ? DIFF_STARTS : DIFF_ENDS), 545 pos_diff((change.new_end_position - change.new_start_position) - 546 (change.end_position - change.start_position)) {} 547 548 static bool LessThan(const SourcePositionEvent& a, 549 const SourcePositionEvent& b) { 550 if (a.position != b.position) return a.position < b.position; 551 if (a.type != b.type) return a.type < b.type; 552 if (a.type == LITERAL_STARTS && b.type == LITERAL_STARTS) { 553 // If the literals start in the same position, we want the one with the 554 // furthest (i.e. largest) end position to be first. 555 if (a.literal->end_position() != b.literal->end_position()) { 556 return a.literal->end_position() > b.literal->end_position(); 557 } 558 // If they also end in the same position, we want the first in order of 559 // literal ids to be first. 560 return a.literal->function_literal_id() < 561 b.literal->function_literal_id(); 562 } else if (a.type == LITERAL_ENDS && b.type == LITERAL_ENDS) { 563 // If the literals end in the same position, we want the one with the 564 // nearest (i.e. largest) start position to be first. 565 if (a.literal->start_position() != b.literal->start_position()) { 566 return a.literal->start_position() > b.literal->start_position(); 567 } 568 // If they also end in the same position, we want the last in order of 569 // literal ids to be first. 570 return a.literal->function_literal_id() > 571 b.literal->function_literal_id(); 572 } else { 573 return a.pos_diff < b.pos_diff; 574 } 575 } 576}; 577 578struct FunctionLiteralChange { 579 // If any of start/end position is kNoSourcePosition, this literal is 580 // considered damaged and will not be mapped and edited at all. 581 int new_start_position; 582 int new_end_position; 583 bool has_changes; 584 FunctionLiteral* outer_literal; 585 586 explicit FunctionLiteralChange(int new_start_position, FunctionLiteral* outer) 587 : new_start_position(new_start_position), 588 new_end_position(kNoSourcePosition), 589 has_changes(false), 590 outer_literal(outer) {} 591}; 592 593using FunctionLiteralChanges = 594 std::unordered_map<FunctionLiteral*, FunctionLiteralChange>; 595void CalculateFunctionLiteralChanges( 596 const std::vector<FunctionLiteral*>& literals, 597 const std::vector<SourceChangeRange>& diffs, 598 FunctionLiteralChanges* result) { 599 std::vector<SourcePositionEvent> events; 600 events.reserve(literals.size() * 2 + diffs.size() * 2); 601 for (FunctionLiteral* literal : literals) { 602 events.emplace_back(literal, true); 603 events.emplace_back(literal, false); 604 } 605 for (const SourceChangeRange& diff : diffs) { 606 events.emplace_back(diff, true); 607 events.emplace_back(diff, false); 608 } 609 std::sort(events.begin(), events.end(), SourcePositionEvent::LessThan); 610 bool inside_diff = false; 611 int delta = 0; 612 std::stack<std::pair<FunctionLiteral*, FunctionLiteralChange>> literal_stack; 613 for (const SourcePositionEvent& event : events) { 614 switch (event.type) { 615 case SourcePositionEvent::DIFF_ENDS: 616 DCHECK(inside_diff); 617 inside_diff = false; 618 delta += event.pos_diff; 619 break; 620 case SourcePositionEvent::LITERAL_ENDS: { 621 DCHECK_EQ(literal_stack.top().first, event.literal); 622 FunctionLiteralChange& change = literal_stack.top().second; 623 change.new_end_position = inside_diff 624 ? kNoSourcePosition 625 : event.literal->end_position() + delta; 626 result->insert(literal_stack.top()); 627 literal_stack.pop(); 628 break; 629 } 630 case SourcePositionEvent::LITERAL_STARTS: 631 literal_stack.push(std::make_pair( 632 event.literal, 633 FunctionLiteralChange( 634 inside_diff ? kNoSourcePosition 635 : event.literal->start_position() + delta, 636 literal_stack.empty() ? nullptr : literal_stack.top().first))); 637 break; 638 case SourcePositionEvent::DIFF_STARTS: 639 DCHECK(!inside_diff); 640 inside_diff = true; 641 if (!literal_stack.empty()) { 642 // Note that outer literal has not necessarily changed, unless the 643 // diff goes past the end of this literal. In this case, we'll mark 644 // this function as damaged and parent as changed later in 645 // MapLiterals. 646 literal_stack.top().second.has_changes = true; 647 } 648 break; 649 } 650 } 651} 652 653// Function which has not changed itself, but if any variable in its 654// outer context has been added/removed, we must consider this function 655// as damaged and not update references to it. 656// This is because old compiled function has hardcoded references to 657// it's outer context. 658bool HasChangedScope(FunctionLiteral* a, FunctionLiteral* b) { 659 Scope* scope_a = a->scope()->outer_scope(); 660 Scope* scope_b = b->scope()->outer_scope(); 661 while (scope_a && scope_b) { 662 std::unordered_map<int, Handle<String>> vars; 663 for (Variable* var : *scope_a->locals()) { 664 if (!var->IsContextSlot()) continue; 665 vars[var->index()] = var->name(); 666 } 667 for (Variable* var : *scope_b->locals()) { 668 if (!var->IsContextSlot()) continue; 669 auto it = vars.find(var->index()); 670 if (it == vars.end()) return true; 671 if (*it->second != *var->name()) return true; 672 } 673 scope_a = scope_a->outer_scope(); 674 scope_b = scope_b->outer_scope(); 675 } 676 return scope_a != scope_b; 677} 678 679enum ChangeState { UNCHANGED, CHANGED, DAMAGED }; 680 681using LiteralMap = std::unordered_map<FunctionLiteral*, FunctionLiteral*>; 682void MapLiterals(const FunctionLiteralChanges& changes, 683 const std::vector<FunctionLiteral*>& new_literals, 684 LiteralMap* unchanged, LiteralMap* changed) { 685 // Track the top-level script function separately as it can overlap fully with 686 // another function, e.g. the script "()=>42". 687 const std::pair<int, int> kTopLevelMarker = std::make_pair(-1, -1); 688 std::map<std::pair<int, int>, FunctionLiteral*> position_to_new_literal; 689 for (FunctionLiteral* literal : new_literals) { 690 DCHECK(literal->start_position() != kNoSourcePosition); 691 DCHECK(literal->end_position() != kNoSourcePosition); 692 std::pair<int, int> key = 693 literal->function_literal_id() == kFunctionLiteralIdTopLevel 694 ? kTopLevelMarker 695 : std::make_pair(literal->start_position(), 696 literal->end_position()); 697 // Make sure there are no duplicate keys. 698 DCHECK_EQ(position_to_new_literal.find(key), position_to_new_literal.end()); 699 position_to_new_literal[key] = literal; 700 } 701 LiteralMap mappings; 702 std::unordered_map<FunctionLiteral*, ChangeState> change_state; 703 for (const auto& change_pair : changes) { 704 FunctionLiteral* literal = change_pair.first; 705 const FunctionLiteralChange& change = change_pair.second; 706 std::pair<int, int> key = 707 literal->function_literal_id() == kFunctionLiteralIdTopLevel 708 ? kTopLevelMarker 709 : std::make_pair(change.new_start_position, 710 change.new_end_position); 711 auto it = position_to_new_literal.find(key); 712 if (it == position_to_new_literal.end() || 713 HasChangedScope(literal, it->second)) { 714 change_state[literal] = ChangeState::DAMAGED; 715 if (!change.outer_literal) continue; 716 if (change_state[change.outer_literal] != ChangeState::DAMAGED) { 717 change_state[change.outer_literal] = ChangeState::CHANGED; 718 } 719 } else { 720 mappings[literal] = it->second; 721 if (change_state.find(literal) == change_state.end()) { 722 change_state[literal] = 723 change.has_changes ? ChangeState::CHANGED : ChangeState::UNCHANGED; 724 } 725 } 726 } 727 for (const auto& mapping : mappings) { 728 if (change_state[mapping.first] == ChangeState::UNCHANGED) { 729 (*unchanged)[mapping.first] = mapping.second; 730 } else if (change_state[mapping.first] == ChangeState::CHANGED) { 731 (*changed)[mapping.first] = mapping.second; 732 } 733 } 734} 735 736class CollectFunctionLiterals final 737 : public AstTraversalVisitor<CollectFunctionLiterals> { 738 public: 739 CollectFunctionLiterals(Isolate* isolate, AstNode* root) 740 : AstTraversalVisitor<CollectFunctionLiterals>(isolate, root) {} 741 void VisitFunctionLiteral(FunctionLiteral* lit) { 742 AstTraversalVisitor::VisitFunctionLiteral(lit); 743 literals_->push_back(lit); 744 } 745 void Run(std::vector<FunctionLiteral*>* literals) { 746 literals_ = literals; 747 AstTraversalVisitor::Run(); 748 literals_ = nullptr; 749 } 750 751 private: 752 std::vector<FunctionLiteral*>* literals_; 753}; 754 755bool ParseScript(Isolate* isolate, Handle<Script> script, ParseInfo* parse_info, 756 bool compile_as_well, std::vector<FunctionLiteral*>* literals, 757 debug::LiveEditResult* result) { 758 v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate)); 759 Handle<SharedFunctionInfo> shared; 760 bool success = false; 761 if (compile_as_well) { 762 success = Compiler::CompileForLiveEdit(parse_info, script, isolate) 763 .ToHandle(&shared); 764 } else { 765 success = parsing::ParseProgram(parse_info, script, isolate, 766 parsing::ReportStatisticsMode::kYes); 767 if (!success) { 768 // Throw the parser error. 769 parse_info->pending_error_handler()->PrepareErrors( 770 isolate, parse_info->ast_value_factory()); 771 parse_info->pending_error_handler()->ReportErrors(isolate, script); 772 } 773 } 774 if (!success) { 775 isolate->OptionalRescheduleException(false); 776 DCHECK(try_catch.HasCaught()); 777 result->message = try_catch.Message()->Get(); 778 auto self = Utils::OpenHandle(*try_catch.Message()); 779 auto msg = i::Handle<i::JSMessageObject>::cast(self); 780 result->line_number = msg->GetLineNumber(); 781 result->column_number = msg->GetColumnNumber(); 782 result->status = debug::LiveEditResult::COMPILE_ERROR; 783 return false; 784 } 785 CollectFunctionLiterals(isolate, parse_info->literal()).Run(literals); 786 return true; 787} 788 789struct FunctionData { 790 explicit FunctionData(FunctionLiteral* literal) 791 : literal(literal), stack_position(NOT_ON_STACK) {} 792 793 FunctionLiteral* literal; 794 MaybeHandle<SharedFunctionInfo> shared; 795 std::vector<Handle<JSFunction>> js_functions; 796 std::vector<Handle<JSGeneratorObject>> running_generators; 797 // In case of multiple functions with different stack position, the latest 798 // one (in the order below) is used, since it is the most restrictive. 799 // This is important only for functions to be restarted. 800 enum StackPosition { NOT_ON_STACK, ON_STACK }; 801 StackPosition stack_position; 802}; 803 804class FunctionDataMap : public ThreadVisitor { 805 public: 806 void AddInterestingLiteral(int script_id, FunctionLiteral* literal) { 807 map_.emplace(GetFuncId(script_id, literal), FunctionData{literal}); 808 } 809 810 bool Lookup(SharedFunctionInfo sfi, FunctionData** data) { 811 int start_position = sfi.StartPosition(); 812 if (!sfi.script().IsScript() || start_position == -1) { 813 return false; 814 } 815 Script script = Script::cast(sfi.script()); 816 return Lookup(GetFuncId(script.id(), sfi), data); 817 } 818 819 bool Lookup(Handle<Script> script, FunctionLiteral* literal, 820 FunctionData** data) { 821 return Lookup(GetFuncId(script->id(), literal), data); 822 } 823 824 void Fill(Isolate* isolate) { 825 { 826 HeapObjectIterator iterator(isolate->heap(), 827 HeapObjectIterator::kFilterUnreachable); 828 for (HeapObject obj = iterator.Next(); !obj.is_null(); 829 obj = iterator.Next()) { 830 if (obj.IsSharedFunctionInfo()) { 831 SharedFunctionInfo sfi = SharedFunctionInfo::cast(obj); 832 FunctionData* data = nullptr; 833 if (!Lookup(sfi, &data)) continue; 834 data->shared = handle(sfi, isolate); 835 } else if (obj.IsJSFunction()) { 836 JSFunction js_function = JSFunction::cast(obj); 837 SharedFunctionInfo sfi = js_function.shared(); 838 FunctionData* data = nullptr; 839 if (!Lookup(sfi, &data)) continue; 840 data->js_functions.emplace_back(js_function, isolate); 841 } else if (obj.IsJSGeneratorObject()) { 842 JSGeneratorObject gen = JSGeneratorObject::cast(obj); 843 if (gen.is_closed()) continue; 844 SharedFunctionInfo sfi = gen.function().shared(); 845 FunctionData* data = nullptr; 846 if (!Lookup(sfi, &data)) continue; 847 data->running_generators.emplace_back(gen, isolate); 848 } 849 } 850 } 851 852 // Visit the current thread stack. 853 VisitThread(isolate, isolate->thread_local_top()); 854 855 // Visit the stacks of all archived threads. 856 isolate->thread_manager()->IterateArchivedThreads(this); 857 } 858 859 private: 860 // Unique id for a function: script_id + start_position, where start_position 861 // is special cased to -1 for top-level so that it does not overlap with a 862 // function whose start position is 0. 863 using FuncId = std::pair<int, int>; 864 865 FuncId GetFuncId(int script_id, FunctionLiteral* literal) { 866 int start_position = literal->start_position(); 867 if (literal->function_literal_id() == 0) { 868 // This is the top-level script function literal, so special case its 869 // start position 870 DCHECK_EQ(start_position, 0); 871 start_position = -1; 872 } 873 return FuncId(script_id, start_position); 874 } 875 876 FuncId GetFuncId(int script_id, SharedFunctionInfo sfi) { 877 DCHECK_EQ(script_id, Script::cast(sfi.script()).id()); 878 int start_position = sfi.StartPosition(); 879 DCHECK_NE(start_position, -1); 880 if (sfi.is_toplevel()) { 881 // This is the top-level function, so special case its start position 882 DCHECK_EQ(start_position, 0); 883 start_position = -1; 884 } 885 return FuncId(script_id, start_position); 886 } 887 888 bool Lookup(FuncId id, FunctionData** data) { 889 auto it = map_.find(id); 890 if (it == map_.end()) return false; 891 *data = &it->second; 892 return true; 893 } 894 895 void VisitThread(Isolate* isolate, ThreadLocalTop* top) override { 896 for (JavaScriptFrameIterator it(isolate, top); !it.done(); it.Advance()) { 897 std::vector<Handle<SharedFunctionInfo>> sfis; 898 it.frame()->GetFunctions(&sfis); 899 for (auto& sfi : sfis) { 900 FunctionData* data = nullptr; 901 if (!Lookup(*sfi, &data)) continue; 902 data->stack_position = FunctionData::ON_STACK; 903 } 904 } 905 } 906 907 std::map<FuncId, FunctionData> map_; 908}; 909 910bool CanPatchScript(const LiteralMap& changed, Handle<Script> script, 911 Handle<Script> new_script, 912 FunctionDataMap& function_data_map, 913 debug::LiveEditResult* result) { 914 for (const auto& mapping : changed) { 915 FunctionData* data = nullptr; 916 function_data_map.Lookup(script, mapping.first, &data); 917 FunctionData* new_data = nullptr; 918 function_data_map.Lookup(new_script, mapping.second, &new_data); 919 Handle<SharedFunctionInfo> sfi; 920 if (!data->shared.ToHandle(&sfi)) { 921 continue; 922 } else if (data->stack_position == FunctionData::ON_STACK) { 923 result->status = debug::LiveEditResult::BLOCKED_BY_ACTIVE_FUNCTION; 924 return false; 925 } else if (!data->running_generators.empty()) { 926 result->status = debug::LiveEditResult::BLOCKED_BY_RUNNING_GENERATOR; 927 return false; 928 } 929 } 930 return true; 931} 932 933void TranslateSourcePositionTable(Isolate* isolate, Handle<BytecodeArray> code, 934 const std::vector<SourceChangeRange>& diffs) { 935 Zone zone(isolate->allocator(), ZONE_NAME); 936 SourcePositionTableBuilder builder(&zone); 937 938 Handle<ByteArray> source_position_table(code->SourcePositionTable(), isolate); 939 for (SourcePositionTableIterator iterator(*source_position_table); 940 !iterator.done(); iterator.Advance()) { 941 SourcePosition position = iterator.source_position(); 942 position.SetScriptOffset( 943 LiveEdit::TranslatePosition(diffs, position.ScriptOffset())); 944 builder.AddPosition(iterator.code_offset(), position, 945 iterator.is_statement()); 946 } 947 948 Handle<ByteArray> new_source_position_table( 949 builder.ToSourcePositionTable(isolate)); 950 code->set_source_position_table(*new_source_position_table, kReleaseStore); 951 LOG_CODE_EVENT(isolate, 952 CodeLinePosInfoRecordEvent(code->GetFirstBytecodeAddress(), 953 *new_source_position_table, 954 JitCodeEvent::BYTE_CODE)); 955} 956 957void UpdatePositions(Isolate* isolate, Handle<SharedFunctionInfo> sfi, 958 const std::vector<SourceChangeRange>& diffs) { 959 int old_start_position = sfi->StartPosition(); 960 int new_start_position = 961 LiveEdit::TranslatePosition(diffs, old_start_position); 962 int new_end_position = LiveEdit::TranslatePosition(diffs, sfi->EndPosition()); 963 int new_function_token_position = 964 LiveEdit::TranslatePosition(diffs, sfi->function_token_position()); 965 sfi->SetPosition(new_start_position, new_end_position); 966 sfi->SetFunctionTokenPosition(new_function_token_position, 967 new_start_position); 968 if (sfi->HasBytecodeArray()) { 969 TranslateSourcePositionTable( 970 isolate, handle(sfi->GetBytecodeArray(isolate), isolate), diffs); 971 } 972} 973} // anonymous namespace 974 975void LiveEdit::PatchScript(Isolate* isolate, Handle<Script> script, 976 Handle<String> new_source, bool preview, 977 debug::LiveEditResult* result) { 978 std::vector<SourceChangeRange> diffs; 979 LiveEdit::CompareStrings(isolate, 980 handle(String::cast(script->source()), isolate), 981 new_source, &diffs); 982 if (diffs.empty()) { 983 result->status = debug::LiveEditResult::OK; 984 return; 985 } 986 987 ReusableUnoptimizedCompileState reusable_state(isolate); 988 989 UnoptimizedCompileState compile_state; 990 UnoptimizedCompileFlags flags = 991 UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); 992 flags.set_is_eager(true); 993 ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state); 994 std::vector<FunctionLiteral*> literals; 995 if (!ParseScript(isolate, script, &parse_info, false, &literals, result)) 996 return; 997 998 Handle<Script> new_script = isolate->factory()->CloneScript(script); 999 new_script->set_source(*new_source); 1000 UnoptimizedCompileState new_compile_state; 1001 UnoptimizedCompileFlags new_flags = 1002 UnoptimizedCompileFlags::ForScriptCompile(isolate, *new_script); 1003 new_flags.set_is_eager(true); 1004 ParseInfo new_parse_info(isolate, new_flags, &new_compile_state, 1005 &reusable_state); 1006 std::vector<FunctionLiteral*> new_literals; 1007 if (!ParseScript(isolate, new_script, &new_parse_info, true, &new_literals, 1008 result)) { 1009 return; 1010 } 1011 1012 FunctionLiteralChanges literal_changes; 1013 CalculateFunctionLiteralChanges(literals, diffs, &literal_changes); 1014 1015 LiteralMap changed; 1016 LiteralMap unchanged; 1017 MapLiterals(literal_changes, new_literals, &unchanged, &changed); 1018 1019 FunctionDataMap function_data_map; 1020 for (const auto& mapping : changed) { 1021 function_data_map.AddInterestingLiteral(script->id(), mapping.first); 1022 function_data_map.AddInterestingLiteral(new_script->id(), mapping.second); 1023 } 1024 for (const auto& mapping : unchanged) { 1025 function_data_map.AddInterestingLiteral(script->id(), mapping.first); 1026 } 1027 function_data_map.Fill(isolate); 1028 1029 if (!CanPatchScript(changed, script, new_script, function_data_map, result)) { 1030 return; 1031 } 1032 1033 if (preview) { 1034 result->status = debug::LiveEditResult::OK; 1035 return; 1036 } 1037 1038 // Patching a script means that the bytecode on the stack may no longer 1039 // correspond to the bytecode of the JSFunction for that frame. As a result 1040 // it is no longer safe to flush bytecode since we might flush the new 1041 // bytecode for a JSFunction that is on the stack with an old bytecode, which 1042 // breaks the invariant that any JSFunction active on the stack is compiled. 1043 isolate->set_disable_bytecode_flushing(true); 1044 1045 std::map<int, int> start_position_to_unchanged_id; 1046 for (const auto& mapping : unchanged) { 1047 FunctionData* data = nullptr; 1048 if (!function_data_map.Lookup(script, mapping.first, &data)) continue; 1049 Handle<SharedFunctionInfo> sfi; 1050 if (!data->shared.ToHandle(&sfi)) continue; 1051 DCHECK_EQ(sfi->script(), *script); 1052 1053 isolate->compilation_cache()->Remove(sfi); 1054 isolate->debug()->DeoptimizeFunction(sfi); 1055 if (sfi->HasDebugInfo()) { 1056 Handle<DebugInfo> debug_info(sfi->GetDebugInfo(), isolate); 1057 isolate->debug()->RemoveBreakInfoAndMaybeFree(debug_info); 1058 } 1059 SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, sfi); 1060 UpdatePositions(isolate, sfi, diffs); 1061 1062 sfi->set_script(*new_script); 1063 sfi->set_function_literal_id(mapping.second->function_literal_id()); 1064 new_script->shared_function_infos().Set( 1065 mapping.second->function_literal_id(), HeapObjectReference::Weak(*sfi)); 1066 DCHECK_EQ(sfi->function_literal_id(), 1067 mapping.second->function_literal_id()); 1068 1069 // Save the new start_position -> id mapping, so that we can recover it when 1070 // iterating over changed functions' constant pools. 1071 start_position_to_unchanged_id[mapping.second->start_position()] = 1072 mapping.second->function_literal_id(); 1073 1074 if (sfi->HasUncompiledDataWithPreparseData()) { 1075 sfi->ClearPreparseData(); 1076 } 1077 1078 for (auto& js_function : data->js_functions) { 1079 js_function->set_raw_feedback_cell( 1080 *isolate->factory()->many_closures_cell()); 1081 if (!js_function->is_compiled()) continue; 1082 IsCompiledScope is_compiled_scope( 1083 js_function->shared().is_compiled_scope(isolate)); 1084 JSFunction::EnsureFeedbackVector(isolate, js_function, 1085 &is_compiled_scope); 1086 } 1087 1088 if (!sfi->HasBytecodeArray()) continue; 1089 FixedArray constants = sfi->GetBytecodeArray(isolate).constant_pool(); 1090 for (int i = 0; i < constants.length(); ++i) { 1091 if (!constants.get(i).IsSharedFunctionInfo()) continue; 1092 data = nullptr; 1093 if (!function_data_map.Lookup(SharedFunctionInfo::cast(constants.get(i)), 1094 &data)) { 1095 continue; 1096 } 1097 auto change_it = changed.find(data->literal); 1098 if (change_it == changed.end()) continue; 1099 if (!function_data_map.Lookup(new_script, change_it->second, &data)) { 1100 continue; 1101 } 1102 Handle<SharedFunctionInfo> new_sfi; 1103 if (!data->shared.ToHandle(&new_sfi)) continue; 1104 constants.set(i, *new_sfi); 1105 } 1106 } 1107 for (const auto& mapping : changed) { 1108 FunctionData* data = nullptr; 1109 if (!function_data_map.Lookup(new_script, mapping.second, &data)) continue; 1110 Handle<SharedFunctionInfo> new_sfi = data->shared.ToHandleChecked(); 1111 DCHECK_EQ(new_sfi->script(), *new_script); 1112 1113 if (!function_data_map.Lookup(script, mapping.first, &data)) continue; 1114 Handle<SharedFunctionInfo> sfi; 1115 if (!data->shared.ToHandle(&sfi)) continue; 1116 1117 isolate->debug()->DeoptimizeFunction(sfi); 1118 isolate->compilation_cache()->Remove(sfi); 1119 for (auto& js_function : data->js_functions) { 1120 js_function->set_shared(*new_sfi); 1121 js_function->set_code(js_function->shared().GetCode(), kReleaseStore); 1122 1123 js_function->set_raw_feedback_cell( 1124 *isolate->factory()->many_closures_cell()); 1125 if (!js_function->is_compiled()) continue; 1126 IsCompiledScope is_compiled_scope( 1127 js_function->shared().is_compiled_scope(isolate)); 1128 JSFunction::EnsureFeedbackVector(isolate, js_function, 1129 &is_compiled_scope); 1130 } 1131 } 1132 SharedFunctionInfo::ScriptIterator it(isolate, *new_script); 1133 for (SharedFunctionInfo sfi = it.Next(); !sfi.is_null(); sfi = it.Next()) { 1134 if (!sfi.HasBytecodeArray()) continue; 1135 FixedArray constants = sfi.GetBytecodeArray(isolate).constant_pool(); 1136 for (int i = 0; i < constants.length(); ++i) { 1137 if (!constants.get(i).IsSharedFunctionInfo()) continue; 1138 SharedFunctionInfo inner_sfi = SharedFunctionInfo::cast(constants.get(i)); 1139 // See if there is a mapping from this function's start position to a 1140 // unchanged function's id. 1141 auto unchanged_it = 1142 start_position_to_unchanged_id.find(inner_sfi.StartPosition()); 1143 if (unchanged_it == start_position_to_unchanged_id.end()) continue; 1144 1145 // Grab that function id from the new script's SFI list, which should have 1146 // already been updated in in the unchanged pass. 1147 SharedFunctionInfo old_unchanged_inner_sfi = 1148 SharedFunctionInfo::cast(new_script->shared_function_infos() 1149 .Get(unchanged_it->second) 1150 ->GetHeapObject()); 1151 if (old_unchanged_inner_sfi == inner_sfi) continue; 1152 DCHECK_NE(old_unchanged_inner_sfi, inner_sfi); 1153 // Now some sanity checks. Make sure that the unchanged SFI has already 1154 // been processed and patched to be on the new script ... 1155 DCHECK_EQ(old_unchanged_inner_sfi.script(), *new_script); 1156 constants.set(i, old_unchanged_inner_sfi); 1157 } 1158 } 1159#ifdef DEBUG 1160 { 1161 // Check that all the functions in the new script are valid, that their 1162 // function literals match what is expected, and that start positions are 1163 // unique. 1164 DisallowGarbageCollection no_gc; 1165 1166 SharedFunctionInfo::ScriptIterator script_it(isolate, *new_script); 1167 std::set<int> start_positions; 1168 for (SharedFunctionInfo sfi = script_it.Next(); !sfi.is_null(); 1169 sfi = script_it.Next()) { 1170 DCHECK_EQ(sfi.script(), *new_script); 1171 DCHECK_EQ(sfi.function_literal_id(), script_it.CurrentIndex()); 1172 // Don't check the start position of the top-level function, as it can 1173 // overlap with a function in the script. 1174 if (sfi.is_toplevel()) { 1175 DCHECK_EQ(start_positions.find(sfi.StartPosition()), 1176 start_positions.end()); 1177 start_positions.insert(sfi.StartPosition()); 1178 } 1179 1180 if (!sfi.HasBytecodeArray()) continue; 1181 // Check that all the functions in this function's constant pool are also 1182 // on the new script, and that their id matches their index in the new 1183 // scripts function list. 1184 FixedArray constants = sfi.GetBytecodeArray(isolate).constant_pool(); 1185 for (int i = 0; i < constants.length(); ++i) { 1186 if (!constants.get(i).IsSharedFunctionInfo()) continue; 1187 SharedFunctionInfo inner_sfi = 1188 SharedFunctionInfo::cast(constants.get(i)); 1189 DCHECK_EQ(inner_sfi.script(), *new_script); 1190 DCHECK_EQ(inner_sfi, new_script->shared_function_infos() 1191 .Get(inner_sfi.function_literal_id()) 1192 ->GetHeapObject()); 1193 } 1194 } 1195 } 1196#endif 1197 1198 int script_id = script->id(); 1199 script->set_id(new_script->id()); 1200 new_script->set_id(script_id); 1201 result->status = debug::LiveEditResult::OK; 1202 result->script = ToApiHandle<v8::debug::Script>(new_script); 1203} 1204 1205void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1, 1206 Handle<String> s2, 1207 std::vector<SourceChangeRange>* diffs) { 1208 s1 = String::Flatten(isolate, s1); 1209 s2 = String::Flatten(isolate, s2); 1210 1211 LineEndsWrapper line_ends1(isolate, s1); 1212 LineEndsWrapper line_ends2(isolate, s2); 1213 1214 LineArrayCompareInput input(s1, s2, line_ends1, line_ends2); 1215 TokenizingLineArrayCompareOutput output(isolate, line_ends1, line_ends2, s1, 1216 s2, diffs); 1217 1218 NarrowDownInput(&input, &output); 1219 1220 Comparator::CalculateDifference(&input, &output); 1221} 1222 1223int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& diffs, 1224 int position) { 1225 auto it = std::lower_bound(diffs.begin(), diffs.end(), position, 1226 [](const SourceChangeRange& change, int position) { 1227 return change.end_position < position; 1228 }); 1229 if (it != diffs.end() && position == it->end_position) { 1230 return it->new_end_position; 1231 } 1232 if (it == diffs.begin()) return position; 1233 DCHECK(it == diffs.end() || position <= it->start_position); 1234 it = std::prev(it); 1235 return position + (it->new_end_position - it->end_position); 1236} 1237} // namespace internal 1238} // namespace v8 1239