v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
liveedit.cc
Go to the documentation of this file.
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
6
7#include <optional>
8
9#include "src/api/api-inl.h"
11#include "src/ast/ast.h"
12#include "src/ast/scopes.h"
16#include "src/common/globals.h"
19#include "src/debug/debug.h"
23#include "src/logging/log.h"
28#include "src/parsing/parsing.h"
29
30namespace v8 {
31namespace internal {
32namespace {
33
34bool CompareSubstrings(DirectHandle<String> s1, int pos1,
35 DirectHandle<String> s2, int pos2, int len) {
36 for (int i = 0; i < len; i++) {
37 if (s1->Get(i + pos1) != s2->Get(i + pos2)) return false;
38 }
39 return true;
40}
41
42// Additional to Input interface. Lets switch Input range to subrange.
43// More elegant way would be to wrap one Input as another Input object
44// and translate positions there, but that would cost us additional virtual
45// call per comparison.
46class SubrangableInput : public Comparator::Input {
47 public:
48 virtual void SetSubrange1(int offset, int len) = 0;
49 virtual void SetSubrange2(int offset, int len) = 0;
50};
51
52
53class SubrangableOutput : public Comparator::Output {
54 public:
55 virtual void SetSubrange1(int offset, int len) = 0;
56 virtual void SetSubrange2(int offset, int len) = 0;
57};
58
59// Finds common prefix and suffix in input. This parts shouldn't take space in
60// linear programming table. Enable subranging in input and output.
61void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) {
62 const int len1 = input->GetLength1();
63 const int len2 = input->GetLength2();
64
65 int common_prefix_len;
66 int common_suffix_len;
67
68 {
69 common_prefix_len = 0;
70 int prefix_limit = std::min(len1, len2);
71 while (common_prefix_len < prefix_limit &&
72 input->Equals(common_prefix_len, common_prefix_len)) {
73 common_prefix_len++;
74 }
75
76 common_suffix_len = 0;
77 int suffix_limit =
78 std::min(len1 - common_prefix_len, len2 - common_prefix_len);
79
80 while (common_suffix_len < suffix_limit &&
81 input->Equals(len1 - common_suffix_len - 1,
82 len2 - common_suffix_len - 1)) {
83 common_suffix_len++;
84 }
85 }
86
87 if (common_prefix_len > 0 || common_suffix_len > 0) {
88 int new_len1 = len1 - common_suffix_len - common_prefix_len;
89 int new_len2 = len2 - common_suffix_len - common_prefix_len;
90
91 input->SetSubrange1(common_prefix_len, new_len1);
92 input->SetSubrange2(common_prefix_len, new_len2);
93
94 output->SetSubrange1(common_prefix_len, new_len1);
95 output->SetSubrange2(common_prefix_len, new_len2);
96 }
97}
98
99// Represents 2 strings as 2 arrays of tokens.
100// TODO(LiveEdit): Currently it's actually an array of charactres.
101// Make array of tokens instead.
102class TokensCompareInput : public Comparator::Input {
103 public:
104 TokensCompareInput(Handle<String> s1, int offset1, int len1,
105 Handle<String> s2, int offset2, int len2)
106 : s1_(s1), offset1_(offset1), len1_(len1),
107 s2_(s2), offset2_(offset2), len2_(len2) {
108 }
109 int GetLength1() override { return len1_; }
110 int GetLength2() override { return len2_; }
111 bool Equals(int index1, int index2) override {
112 return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2);
113 }
114
115 private:
116 Handle<String> s1_;
118 int len1_;
119 Handle<String> s2_;
121 int len2_;
122};
123
124// Stores compare result in std::vector. Converts substring positions
125// to absolute positions.
126class TokensCompareOutput : public Comparator::Output {
127 public:
128 TokensCompareOutput(int offset1, int offset2,
129 std::vector<SourceChangeRange>* output)
130 : output_(output), offset1_(offset1), offset2_(offset2) {}
131
132 void AddChunk(int pos1, int pos2, int len1, int len2) override {
133 output_->emplace_back(
134 SourceChangeRange{pos1 + offset1_, pos1 + len1 + offset1_,
135 pos2 + offset2_, pos2 + offset2_ + len2});
136 }
137
138 private:
139 std::vector<SourceChangeRange>* output_;
140 int offset1_;
141 int offset2_;
142};
143
144// Wraps raw n-elements line_ends array as a list of n+1 lines. The last line
145// never has terminating new line character.
146class LineEndsWrapper {
147 public:
148 explicit LineEndsWrapper(Isolate* isolate, DirectHandle<String> string)
149 : ends_array_(String::CalculateLineEnds(isolate, string, false)),
150 string_len_(string->length()) {}
151 int length() {
152 return ends_array_->length() + 1;
153 }
154 // Returns start for any line including start of the imaginary line after
155 // the last line.
156 int GetLineStart(int index) { return index == 0 ? 0 : GetLineEnd(index - 1); }
157 int GetLineEnd(int index) {
158 if (index == ends_array_->length()) {
159 // End of the last line is always an end of the whole string.
160 // If the string ends with a new line character, the last line is an
161 // empty string after this character.
162 return string_len_;
163 } else {
164 return GetPosAfterNewLine(index);
165 }
166 }
167
168 private:
171
172 int GetPosAfterNewLine(int index) {
173 return Smi::ToInt(ends_array_->get(index)) + 1;
174 }
175};
176
177// Represents 2 strings as 2 arrays of lines.
178class LineArrayCompareInput : public SubrangableInput {
179 public:
180 LineArrayCompareInput(Handle<String> s1, Handle<String> s2,
181 LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
182 : s1_(s1), s2_(s2), line_ends1_(line_ends1),
183 line_ends2_(line_ends2),
185 subrange_len1_(line_ends1_.length()),
186 subrange_len2_(line_ends2_.length()) {
187 }
188 int GetLength1() override { return subrange_len1_; }
189 int GetLength2() override { return subrange_len2_; }
190 bool Equals(int index1, int index2) override {
191 index1 += subrange_offset1_;
192 index2 += subrange_offset2_;
193
194 int line_start1 = line_ends1_.GetLineStart(index1);
195 int line_start2 = line_ends2_.GetLineStart(index2);
196 int line_end1 = line_ends1_.GetLineEnd(index1);
197 int line_end2 = line_ends2_.GetLineEnd(index2);
198 int len1 = line_end1 - line_start1;
199 int len2 = line_end2 - line_start2;
200 if (len1 != len2) {
201 return false;
202 }
203 return CompareSubstrings(s1_, line_start1, s2_, line_start2,
204 len1);
205 }
206 void SetSubrange1(int offset, int len) override {
208 subrange_len1_ = len;
209 }
210 void SetSubrange2(int offset, int len) override {
212 subrange_len2_ = len;
213 }
214
215 private:
216 Handle<String> s1_;
217 Handle<String> s2_;
218 LineEndsWrapper line_ends1_;
219 LineEndsWrapper line_ends2_;
224};
225
226// Stores compare result in std::vector. For each chunk tries to conduct
227// a fine-grained nested diff token-wise.
228class TokenizingLineArrayCompareOutput : public SubrangableOutput {
229 public:
230 TokenizingLineArrayCompareOutput(Isolate* isolate, LineEndsWrapper line_ends1,
231 LineEndsWrapper line_ends2,
232 Handle<String> s1, Handle<String> s2,
233 std::vector<SourceChangeRange>* output)
234 : isolate_(isolate),
235 line_ends1_(line_ends1),
236 line_ends2_(line_ends2),
237 s1_(s1),
238 s2_(s2),
241 output_(output) {}
242
243 void AddChunk(int line_pos1, int line_pos2, int line_len1,
244 int line_len2) override {
245 line_pos1 += subrange_offset1_;
246 line_pos2 += subrange_offset2_;
247
248 int char_pos1 = line_ends1_.GetLineStart(line_pos1);
249 int char_pos2 = line_ends2_.GetLineStart(line_pos2);
250 int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1;
251 int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2;
252
253 if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) {
254 // Chunk is small enough to conduct a nested token-level diff.
255 HandleScope subTaskScope(isolate_);
256
257 TokensCompareInput tokens_input(s1_, char_pos1, char_len1,
258 s2_, char_pos2, char_len2);
259 TokensCompareOutput tokens_output(char_pos1, char_pos2, output_);
260
261 Comparator::CalculateDifference(&tokens_input, &tokens_output);
262 } else {
263 output_->emplace_back(SourceChangeRange{
264 char_pos1, char_pos1 + char_len1, char_pos2, char_pos2 + char_len2});
265 }
266 }
267 void SetSubrange1(int offset, int len) override {
269 }
270 void SetSubrange2(int offset, int len) override {
272 }
273
274 private:
275 static const int CHUNK_LEN_LIMIT = 800;
276
277 Isolate* isolate_;
278 LineEndsWrapper line_ends1_;
279 LineEndsWrapper line_ends2_;
280 Handle<String> s1_;
281 Handle<String> s2_;
284 std::vector<SourceChangeRange>* output_;
285};
286
287struct SourcePositionEvent {
288 enum Type { LITERAL_STARTS, LITERAL_ENDS, DIFF_STARTS, DIFF_ENDS };
289
291 Type type;
292
293 union {
294 FunctionLiteral* literal;
296 };
297
298 SourcePositionEvent(FunctionLiteral* literal, bool is_start)
299 : position(is_start ? literal->start_position()
300 : literal->end_position()),
301 type(is_start ? LITERAL_STARTS : LITERAL_ENDS),
302 literal(literal) {}
303 SourcePositionEvent(const SourceChangeRange& change, bool is_start)
304 : position(is_start ? change.start_position : change.end_position),
305 type(is_start ? DIFF_STARTS : DIFF_ENDS),
307 (change.end_position - change.start_position)) {}
308
309 static bool LessThan(const SourcePositionEvent& a,
310 const SourcePositionEvent& b) {
311 if (a.position != b.position) return a.position < b.position;
312 if (a.type != b.type) return a.type < b.type;
313 if (a.type == LITERAL_STARTS && b.type == LITERAL_STARTS) {
314 // If the literals start in the same position, we want the one with the
315 // furthest (i.e. largest) end position to be first.
316 if (a.literal->end_position() != b.literal->end_position()) {
317 return a.literal->end_position() > b.literal->end_position();
318 }
319 // If they also end in the same position, we want the first in order of
320 // literal ids to be first.
321 return a.literal->function_literal_id() <
322 b.literal->function_literal_id();
323 } else if (a.type == LITERAL_ENDS && b.type == LITERAL_ENDS) {
324 // If the literals end in the same position, we want the one with the
325 // nearest (i.e. largest) start position to be first.
326 if (a.literal->start_position() != b.literal->start_position()) {
327 return a.literal->start_position() > b.literal->start_position();
328 }
329 // If they also end in the same position, we want the last in order of
330 // literal ids to be first.
331 return a.literal->function_literal_id() >
332 b.literal->function_literal_id();
333 } else {
334 return a.pos_diff < b.pos_diff;
335 }
336 }
337};
338
339struct FunctionLiteralChange {
340 // If any of start/end position is kNoSourcePosition, this literal is
341 // considered damaged and will not be mapped and edited at all.
345 FunctionLiteral* outer_literal;
346
347 explicit FunctionLiteralChange(int new_start_position, FunctionLiteral* outer)
350 has_changes(false),
351 outer_literal(outer) {}
352};
353
354using FunctionLiteralChanges =
355 std::unordered_map<FunctionLiteral*, FunctionLiteralChange>;
356void CalculateFunctionLiteralChanges(
357 const std::vector<FunctionLiteral*>& literals,
358 const std::vector<SourceChangeRange>& diffs,
359 FunctionLiteralChanges* result) {
360 std::vector<SourcePositionEvent> events;
361 events.reserve(literals.size() * 2 + diffs.size() * 2);
362 for (FunctionLiteral* literal : literals) {
363 events.emplace_back(literal, true);
364 events.emplace_back(literal, false);
365 }
366 for (const SourceChangeRange& diff : diffs) {
367 events.emplace_back(diff, true);
368 events.emplace_back(diff, false);
369 }
370 std::sort(events.begin(), events.end(), SourcePositionEvent::LessThan);
371 bool inside_diff = false;
372 int delta = 0;
373 std::stack<std::pair<FunctionLiteral*, FunctionLiteralChange>> literal_stack;
374 for (const SourcePositionEvent& event : events) {
375 switch (event.type) {
376 case SourcePositionEvent::DIFF_ENDS:
377 DCHECK(inside_diff);
378 inside_diff = false;
379 delta += event.pos_diff;
380 break;
381 case SourcePositionEvent::LITERAL_ENDS: {
382 DCHECK_EQ(literal_stack.top().first, event.literal);
383 FunctionLiteralChange& change = literal_stack.top().second;
384 change.new_end_position = inside_diff
386 : event.literal->end_position() + delta;
387 result->insert(literal_stack.top());
388 literal_stack.pop();
389 break;
390 }
391 case SourcePositionEvent::LITERAL_STARTS:
392 literal_stack.push(std::make_pair(
393 event.literal,
394 FunctionLiteralChange(
395 inside_diff ? kNoSourcePosition
396 : event.literal->start_position() + delta,
397 literal_stack.empty() ? nullptr : literal_stack.top().first)));
398 break;
399 case SourcePositionEvent::DIFF_STARTS:
400 DCHECK(!inside_diff);
401 inside_diff = true;
402 if (!literal_stack.empty()) {
403 // Note that outer literal has not necessarily changed, unless the
404 // diff goes past the end of this literal. In this case, we'll mark
405 // this function as damaged and parent as changed later in
406 // MapLiterals.
407 literal_stack.top().second.has_changes = true;
408 }
409 break;
410 }
411 }
412}
413
414// Function which has not changed itself, but if any variable in its
415// outer context has been added/removed, we must consider this function
416// as damaged and not update references to it.
417// This is because old compiled function has hardcoded references to
418// it's outer context.
419bool HasChangedScope(FunctionLiteral* a, FunctionLiteral* b) {
420 Scope* scope_a = a->scope()->outer_scope();
421 Scope* scope_b = b->scope()->outer_scope();
422 while (scope_a && scope_b) {
423 std::unordered_map<int, Handle<String>> vars;
424 for (Variable* var : *scope_a->locals()) {
425 if (!var->IsContextSlot()) continue;
426 vars[var->index()] = var->name();
427 }
428 for (Variable* var : *scope_b->locals()) {
429 if (!var->IsContextSlot()) continue;
430 auto it = vars.find(var->index());
431 if (it == vars.end()) return true;
432 if (*it->second != *var->name()) return true;
433 }
434 scope_a = scope_a->outer_scope();
435 scope_b = scope_b->outer_scope();
436 }
437 return scope_a != scope_b;
438}
439
440enum ChangeState { UNCHANGED, CHANGED, DAMAGED };
441
442using LiteralMap = std::unordered_map<FunctionLiteral*, FunctionLiteral*>;
443void MapLiterals(const FunctionLiteralChanges& changes,
444 const std::vector<FunctionLiteral*>& new_literals,
445 LiteralMap* unchanged, LiteralMap* changed) {
446 // Track the top-level script function separately as it can overlap fully with
447 // another function, e.g. the script "()=>42".
448 const std::pair<int, int> kTopLevelMarker = std::make_pair(-1, -1);
449 std::map<std::pair<int, int>, FunctionLiteral*> position_to_new_literal;
450 for (FunctionLiteral* literal : new_literals) {
453 std::pair<int, int> key =
455 ? kTopLevelMarker
456 : std::make_pair(literal->start_position(),
458 // Make sure there are no duplicate keys.
459 DCHECK_EQ(position_to_new_literal.find(key), position_to_new_literal.end());
460 position_to_new_literal[key] = literal;
461 }
462 LiteralMap mappings;
463 std::unordered_map<FunctionLiteral*, ChangeState> change_state;
464 for (const auto& change_pair : changes) {
465 FunctionLiteral* literal = change_pair.first;
466 const FunctionLiteralChange& change = change_pair.second;
467 std::pair<int, int> key =
469 ? kTopLevelMarker
470 : std::make_pair(change.new_start_position,
471 change.new_end_position);
472 auto it = position_to_new_literal.find(key);
473 if (it == position_to_new_literal.end() ||
474 HasChangedScope(literal, it->second)) {
475 change_state[literal] = ChangeState::DAMAGED;
476 if (!change.outer_literal) continue;
477 if (change_state[change.outer_literal] != ChangeState::DAMAGED) {
478 change_state[change.outer_literal] = ChangeState::CHANGED;
479 }
480 } else {
481 mappings[literal] = it->second;
482 if (change_state.find(literal) == change_state.end()) {
483 change_state[literal] =
484 change.has_changes ? ChangeState::CHANGED : ChangeState::UNCHANGED;
485 }
486 }
487 }
488 for (const auto& mapping : mappings) {
489 if (change_state[mapping.first] == ChangeState::UNCHANGED) {
490 (*unchanged)[mapping.first] = mapping.second;
491 } else if (change_state[mapping.first] == ChangeState::CHANGED) {
492 (*changed)[mapping.first] = mapping.second;
493 }
494 }
495}
496
497class CollectFunctionLiterals final
498 : public AstTraversalVisitor<CollectFunctionLiterals> {
499 public:
500 CollectFunctionLiterals(Isolate* isolate, AstNode* root)
501 : AstTraversalVisitor<CollectFunctionLiterals>(isolate, root) {}
502 void VisitFunctionLiteral(FunctionLiteral* lit) {
503 AstTraversalVisitor::VisitFunctionLiteral(lit);
504 literals_->push_back(lit);
505 }
506 void Run(std::vector<FunctionLiteral*>* literals) {
507 literals_ = literals;
509 literals_ = nullptr;
510 }
511
512 private:
513 std::vector<FunctionLiteral*>* literals_;
514};
515
516bool ParseScript(Isolate* isolate, Handle<Script> script, ParseInfo* parse_info,
517 MaybeDirectHandle<ScopeInfo> outer_scope_info,
518 bool compile_as_well, std::vector<FunctionLiteral*>* literals,
519 debug::LiveEditResult* result) {
520 v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
521 DirectHandle<SharedFunctionInfo> shared;
522 bool success = false;
523 if (compile_as_well) {
524 success = Compiler::CompileForLiveEdit(parse_info, script, outer_scope_info,
525 isolate)
526 .ToHandle(&shared);
527 } else {
528 success =
529 parsing::ParseProgram(parse_info, script, outer_scope_info, isolate,
531 if (!success) {
532 // Throw the parser error.
533 parse_info->pending_error_handler()->PrepareErrors(
534 isolate, parse_info->ast_value_factory());
535 parse_info->pending_error_handler()->ReportErrors(isolate, script);
536 }
537 }
538 if (!success) {
539 DCHECK(try_catch.HasCaught());
540 result->message = try_catch.Message()->Get();
542 Utils::OpenDirectHandle(*try_catch.Message());
543 i::JSMessageObject::EnsureSourcePositionsAvailable(isolate, msg);
544 result->line_number = msg->GetLineNumber();
545 result->column_number = msg->GetColumnNumber();
547 return false;
548 }
549 CollectFunctionLiterals(isolate, parse_info->literal()).Run(literals);
550 return true;
551}
552
553struct FunctionData {
554 explicit FunctionData(FunctionLiteral* literal)
555 : literal(literal), stack_position(NOT_ON_STACK) {}
556
557 FunctionLiteral* literal;
558 MaybeHandle<SharedFunctionInfo> shared;
559 std::vector<Handle<JSFunction>> js_functions;
560 std::vector<Handle<JSGeneratorObject>> running_generators;
561 // In case of multiple functions with different stack position, the latest
562 // one (in the order below) is used, since it is the most restrictive.
563 // This is important only for functions to be restarted.
564 enum StackPosition { NOT_ON_STACK, ON_TOP_ONLY, ON_STACK };
565 StackPosition stack_position;
566};
567
568class FunctionDataMap : public ThreadVisitor {
569 public:
570 void AddInterestingLiteral(int script_id, FunctionLiteral* literal) {
571 map_.emplace(GetFuncId(script_id, literal), FunctionData{literal});
572 }
573
574 bool Lookup(Tagged<SharedFunctionInfo> sfi, FunctionData** data) {
575 int start_position = sfi->StartPosition();
576 if (!IsScript(sfi->script()) || start_position == -1) {
577 return false;
578 }
579 Tagged<Script> script = Cast<Script>(sfi->script());
580 return Lookup(GetFuncId(script->id(), sfi), data);
581 }
582
583 bool Lookup(DirectHandle<Script> script, FunctionLiteral* literal,
584 FunctionData** data) {
585 return Lookup(GetFuncId(script->id(), literal), data);
586 }
587
588 void Fill(Isolate* isolate) {
589 {
590 HeapObjectIterator iterator(isolate->heap(),
592 for (Tagged<HeapObject> obj = iterator.Next(); !obj.is_null();
593 obj = iterator.Next()) {
594 if (IsSharedFunctionInfo(obj)) {
596 FunctionData* data = nullptr;
597 if (!Lookup(sfi, &data)) continue;
598 data->shared = handle(sfi, isolate);
599 } else if (IsJSFunction(obj)) {
600 Tagged<JSFunction> js_function = Cast<JSFunction>(obj);
601 Tagged<SharedFunctionInfo> sfi = js_function->shared();
602 FunctionData* data = nullptr;
603 if (!Lookup(sfi, &data)) continue;
604 data->js_functions.emplace_back(js_function, isolate);
605 } else if (IsJSGeneratorObject(obj)) {
607 if (gen->is_closed()) continue;
608 Tagged<SharedFunctionInfo> sfi = gen->function()->shared();
609 FunctionData* data = nullptr;
610 if (!Lookup(sfi, &data)) continue;
611 data->running_generators.emplace_back(gen, isolate);
612 }
613 }
614 }
615
616 // Visit the current thread stack.
617 VisitCurrentThread(isolate);
618
619 // Visit the stacks of all archived threads.
620 isolate->thread_manager()->IterateArchivedThreads(this);
621 }
622
623 private:
624 // Unique id for a function: script_id + start_position, where start_position
625 // is special cased to -1 for top-level so that it does not overlap with a
626 // function whose start position is 0.
627 using FuncId = std::pair<int, int>;
628
629 FuncId GetFuncId(int script_id, FunctionLiteral* literal) {
630 int start_position = literal->start_position();
631 if (literal->function_literal_id() == 0) {
632 // This is the top-level script function literal, so special case its
633 // start position
634 DCHECK_EQ(start_position, 0);
635 start_position = -1;
636 }
637 return FuncId(script_id, start_position);
638 }
639
640 FuncId GetFuncId(int script_id, Tagged<SharedFunctionInfo> sfi) {
641 DCHECK_EQ(script_id, Cast<Script>(sfi->script())->id());
642 int start_position = sfi->StartPosition();
643 DCHECK_NE(start_position, -1);
644 if (sfi->is_toplevel()) {
645 // This is the top-level function, so special case its start position
646 DCHECK_EQ(start_position, 0);
647 start_position = -1;
648 }
649 return FuncId(script_id, start_position);
650 }
651
652 bool Lookup(FuncId id, FunctionData** data) {
653 auto it = map_.find(id);
654 if (it == map_.end()) return false;
655 *data = &it->second;
656 return true;
657 }
658
659 void VisitThread(Isolate* isolate, ThreadLocalTop* top) override {
660 for (JavaScriptStackFrameIterator it(isolate, top); !it.done();
661 it.Advance()) {
662 std::vector<Handle<SharedFunctionInfo>> sfis;
663 it.frame()->GetFunctions(&sfis);
664 for (auto& sfi : sfis) {
665 FunctionData* data = nullptr;
666 if (!Lookup(*sfi, &data)) continue;
667 data->stack_position = FunctionData::ON_STACK;
668 }
669 }
670 }
671
672 void VisitCurrentThread(Isolate* isolate) {
673 // We allow live editing the function that's currently top-of-stack. But
674 // only if no activation of that function is anywhere else on the stack.
675 bool is_top = true;
676 for (DebugStackTraceIterator it(isolate, /* index */ 0); !it.Done();
677 it.Advance(), is_top = false) {
678 auto sfi = it.GetSharedFunctionInfo();
679 if (sfi.is_null()) continue;
680 FunctionData* data = nullptr;
681 if (!Lookup(*sfi, &data)) continue;
682
683 // ON_TOP_ONLY will only be set on the first iteration (and if the frame
684 // can be restarted). Further activations will change the ON_TOP_ONLY to
685 // ON_STACK and prevent the live edit from happening.
686 data->stack_position = is_top && it.CanBeRestarted()
687 ? FunctionData::ON_TOP_ONLY
688 : FunctionData::ON_STACK;
689 }
690 }
691
692 std::map<FuncId, FunctionData> map_;
693};
694
695bool CanPatchScript(const LiteralMap& changed, DirectHandle<Script> script,
696 DirectHandle<Script> new_script,
697 FunctionDataMap& function_data_map,
698 bool allow_top_frame_live_editing,
699 debug::LiveEditResult* result) {
700 for (const auto& mapping : changed) {
701 FunctionData* data = nullptr;
702 function_data_map.Lookup(script, mapping.first, &data);
703 FunctionData* new_data = nullptr;
704 function_data_map.Lookup(new_script, mapping.second, &new_data);
706 if (!data->shared.ToHandle(&sfi)) {
707 continue;
708 } else if (IsModule(sfi->kind())) {
709 DCHECK(script->origin_options().IsModule() && sfi->is_toplevel());
710 result->status =
712 return false;
713 } else if (data->stack_position == FunctionData::ON_STACK) {
715 return false;
716 } else if (!data->running_generators.empty()) {
718 return false;
719 } else if (data->stack_position == FunctionData::ON_TOP_ONLY) {
720 if (!allow_top_frame_live_editing) {
722 return false;
723 }
724 result->restart_top_frame_required = true;
725 }
726 }
727 return true;
728}
729
730void TranslateSourcePositionTable(Isolate* isolate,
731 DirectHandle<BytecodeArray> code,
732 const std::vector<SourceChangeRange>& diffs) {
733 Zone zone(isolate->allocator(), ZONE_NAME);
734 SourcePositionTableBuilder builder(&zone);
735
736 DirectHandle<TrustedByteArray> source_position_table(
737 code->SourcePositionTable(), isolate);
738 for (SourcePositionTableIterator iterator(*source_position_table);
739 !iterator.done(); iterator.Advance()) {
740 SourcePosition position = iterator.source_position();
741 position.SetScriptOffset(
742 LiveEdit::TranslatePosition(diffs, position.ScriptOffset()));
743 builder.AddPosition(iterator.code_offset(), position,
744 iterator.is_statement());
745 }
746
747 DirectHandle<TrustedByteArray> new_source_position_table(
748 builder.ToSourcePositionTable(isolate));
749 code->set_source_position_table(*new_source_position_table, kReleaseStore);
750 LOG_CODE_EVENT(isolate,
751 CodeLinePosInfoRecordEvent(code->GetFirstBytecodeAddress(),
752 *new_source_position_table,
754}
755
756void UpdatePositions(Isolate* isolate, DirectHandle<SharedFunctionInfo> sfi,
757 FunctionLiteral* new_function,
758 const std::vector<SourceChangeRange>& diffs) {
759 sfi->UpdateFromFunctionLiteralForLiveEdit(isolate, new_function);
760 if (sfi->HasBytecodeArray()) {
761 TranslateSourcePositionTable(
762 isolate, direct_handle(sfi->GetBytecodeArray(isolate), isolate), diffs);
763 }
764}
765
766#ifdef DEBUG
767Tagged<ScopeInfo> FindOuterScopeInfoFromScriptSfi(Isolate* isolate,
768 DirectHandle<Script> script) {
769 // We take some SFI from the script and walk outwards until we find the
770 // EVAL_SCOPE. Then we do the same search as `DetermineOuterScopeInfo` and
771 // check that we found the same ScopeInfo.
772 SharedFunctionInfo::ScriptIterator it(isolate, *script);
773 Tagged<ScopeInfo> other_scope_info;
774 for (Tagged<SharedFunctionInfo> sfi = it.Next(); !sfi.is_null();
775 sfi = it.Next()) {
776 if (!sfi->scope_info()->IsEmpty()) {
777 other_scope_info = sfi->scope_info();
778 break;
779 }
780 }
781 if (other_scope_info.is_null()) return other_scope_info;
782
783 while (!other_scope_info->IsEmpty() &&
784 other_scope_info->scope_type() != EVAL_SCOPE &&
785 other_scope_info->HasOuterScopeInfo()) {
786 other_scope_info = other_scope_info->OuterScopeInfo();
787 }
788
789 // This function is only called when we found a ScopeInfo candidate, so
790 // technically the EVAL_SCOPE must have an outer_scope_info. But, the GC can
791 // clean up some ScopeInfos it thinks are no longer needed. Abort the check
792 // in that case.
793 if (!other_scope_info->HasOuterScopeInfo()) return ScopeInfo();
794
795 DCHECK_EQ(other_scope_info->scope_type(), EVAL_SCOPE);
796 other_scope_info = other_scope_info->OuterScopeInfo();
797
798 while (!other_scope_info->IsEmpty() && !other_scope_info->HasContext() &&
799 other_scope_info->HasOuterScopeInfo()) {
800 other_scope_info = other_scope_info->OuterScopeInfo();
801 }
802 return other_scope_info;
803}
804#endif
805
806// For sloppy eval we need to know the ScopeInfo the eval was compiled in and
807// reuse it when we compile the new version of the script.
808MaybeDirectHandle<ScopeInfo> DetermineOuterScopeInfo(
809 Isolate* isolate, DirectHandle<Script> script) {
810 if (!script->has_eval_from_shared()) return kNullMaybeHandle;
811 DCHECK_EQ(script->compilation_type(), Script::CompilationType::kEval);
812 Tagged<ScopeInfo> scope_info = script->eval_from_shared()->scope_info();
813 // Sloppy eval compiles use the ScopeInfo of the context. Let's find it.
814 while (!scope_info->IsEmpty()) {
815 if (scope_info->HasContext()) {
816#ifdef DEBUG
817 Tagged<ScopeInfo> other_scope_info =
818 FindOuterScopeInfoFromScriptSfi(isolate, script);
819 DCHECK_IMPLIES(!other_scope_info.is_null(),
820 scope_info == other_scope_info);
821#endif
822 return direct_handle(scope_info, isolate);
823 } else if (!scope_info->HasOuterScopeInfo()) {
824 break;
825 }
826 scope_info = scope_info->OuterScopeInfo();
827 }
828
829 return kNullMaybeHandle;
830}
831
832} // anonymous namespace
833
835 Handle<String> new_source, bool preview,
836 bool allow_top_frame_live_editing,
838 std::vector<SourceChangeRange> diffs;
840 handle(Cast<String>(script->source()), isolate),
841 new_source, &diffs);
842 if (diffs.empty()) {
844 return;
845 }
846
847 ReusableUnoptimizedCompileState reusable_state(isolate);
848
849 UnoptimizedCompileState compile_state;
852 flags.set_is_eager(true);
853 flags.set_is_reparse(true);
854 ParseInfo parse_info(isolate, flags, &compile_state, &reusable_state);
855 MaybeDirectHandle<ScopeInfo> outer_scope_info =
856 DetermineOuterScopeInfo(isolate, script);
857 std::vector<FunctionLiteral*> literals;
858 if (!ParseScript(isolate, script, &parse_info, outer_scope_info, false,
859 &literals, result))
860 return;
861
862 Handle<Script> new_script =
863 isolate->factory()->CloneScript(script, new_source);
864 UnoptimizedCompileState new_compile_state;
865 UnoptimizedCompileFlags new_flags =
866 UnoptimizedCompileFlags::ForScriptCompile(isolate, *new_script);
867 new_flags.set_is_eager(true);
868 ParseInfo new_parse_info(isolate, new_flags, &new_compile_state,
869 &reusable_state);
870 std::vector<FunctionLiteral*> new_literals;
871 if (!ParseScript(isolate, new_script, &new_parse_info, outer_scope_info, true,
872 &new_literals, result)) {
873 return;
874 }
875
876 FunctionLiteralChanges literal_changes;
877 CalculateFunctionLiteralChanges(literals, diffs, &literal_changes);
878
879 LiteralMap changed;
880 LiteralMap unchanged;
881 MapLiterals(literal_changes, new_literals, &unchanged, &changed);
882
883 FunctionDataMap function_data_map;
884 for (const auto& mapping : changed) {
885 function_data_map.AddInterestingLiteral(script->id(), mapping.first);
886 function_data_map.AddInterestingLiteral(new_script->id(), mapping.second);
887 }
888 for (const auto& mapping : unchanged) {
889 function_data_map.AddInterestingLiteral(script->id(), mapping.first);
890 }
891 function_data_map.Fill(isolate);
892
893 if (!CanPatchScript(changed, script, new_script, function_data_map,
894 allow_top_frame_live_editing, result)) {
895 return;
896 }
897
898 if (preview) {
900 return;
901 }
902
903 // Patching a script means that the bytecode on the stack may no longer
904 // correspond to the bytecode of the JSFunction for that frame. As a result
905 // it is no longer safe to flush bytecode since we might flush the new
906 // bytecode for a JSFunction that is on the stack with an old bytecode, which
907 // breaks the invariant that any JSFunction active on the stack is compiled.
908 isolate->set_disable_bytecode_flushing(true);
909
910 std::map<int, int> start_position_to_unchanged_id;
911 for (const auto& mapping : unchanged) {
912 FunctionData* data = nullptr;
913 if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
915 if (!data->shared.ToHandle(&sfi)) continue;
916 DCHECK_EQ(sfi->script(), *script);
917
918 isolate->compilation_cache()->Remove(sfi);
919 isolate->debug()->DeoptimizeFunction(sfi);
920 if (std::optional<Tagged<DebugInfo>> di = sfi->TryGetDebugInfo(isolate)) {
921 DirectHandle<DebugInfo> debug_info(di.value(), isolate);
922 isolate->debug()->RemoveBreakInfoAndMaybeFree(debug_info);
923 }
925 UpdatePositions(isolate, sfi, mapping.second, diffs);
926
927 sfi->set_script(*new_script, kReleaseStore);
928 sfi->set_function_literal_id(mapping.second->function_literal_id());
929 new_script->infos()->set(mapping.second->function_literal_id(),
930 MakeWeak(*sfi));
931 DCHECK_EQ(sfi->function_literal_id(),
932 mapping.second->function_literal_id());
933
934 // Save the new start_position -> id mapping, so that we can recover it when
935 // iterating over changed functions' constant pools.
936 start_position_to_unchanged_id[mapping.second->start_position()] =
937 mapping.second->function_literal_id();
938
939 if (sfi->HasUncompiledDataWithPreparseData()) {
940 sfi->ClearPreparseData(isolate);
941 }
942
943 for (auto& js_function : data->js_functions) {
944 js_function->set_raw_feedback_cell(
945 *isolate->factory()->many_closures_cell());
946 if (!js_function->is_compiled(isolate)) continue;
947 IsCompiledScope is_compiled_scope(
948 js_function->shared()->is_compiled_scope(isolate));
949 JSFunction::EnsureFeedbackVector(isolate, js_function,
950 &is_compiled_scope);
951 }
952
953 if (!sfi->HasBytecodeArray()) continue;
954 Tagged<TrustedFixedArray> constants =
955 sfi->GetBytecodeArray(isolate)->constant_pool();
956 for (int i = 0; i < constants->length(); ++i) {
957 if (!IsSharedFunctionInfo(constants->get(i))) continue;
958 data = nullptr;
959 if (!function_data_map.Lookup(Cast<SharedFunctionInfo>(constants->get(i)),
960 &data)) {
961 continue;
962 }
963 auto change_it = changed.find(data->literal);
964 if (change_it == changed.end()) continue;
965 if (!function_data_map.Lookup(new_script, change_it->second, &data)) {
966 continue;
967 }
969 if (!data->shared.ToHandle(&new_sfi)) continue;
970 constants->set(i, *new_sfi);
971 }
972 }
973 isolate->LocalsBlockListCacheRehash();
974 for (const auto& mapping : changed) {
975 FunctionData* data = nullptr;
976 if (!function_data_map.Lookup(new_script, mapping.second, &data)) continue;
978 // In most cases the new FunctionLiteral should also have an SFI, but there
979 // are some exceptions. E.g the compiler doesn't create SFIs for
980 // inner functions that are never referenced.
981 if (!data->shared.ToHandle(&new_sfi)) continue;
982 DCHECK_EQ(new_sfi->script(), *new_script);
983
984 if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
986 if (!data->shared.ToHandle(&sfi)) continue;
987
988 isolate->debug()->DeoptimizeFunction(sfi);
989 isolate->compilation_cache()->Remove(sfi);
990 for (auto& js_function : data->js_functions) {
991 js_function->set_raw_feedback_cell(
992 *isolate->factory()->many_closures_cell());
993#ifdef V8_ENABLE_LEAPTIERING
994 auto code = handle(new_sfi->GetCode(isolate), isolate);
995 JSFunction::AllocateDispatchHandle(
996 js_function, isolate,
997 new_sfi->internal_formal_parameter_count_with_receiver(), code);
998#endif
999 js_function->set_shared(*new_sfi);
1000
1001 if (!js_function->is_compiled(isolate)) continue;
1002 IsCompiledScope is_compiled_scope(
1003 js_function->shared()->is_compiled_scope(isolate));
1004 JSFunction::EnsureFeedbackVector(isolate, js_function,
1005 &is_compiled_scope);
1006 }
1007 }
1008 SharedFunctionInfo::ScriptIterator it(isolate, *new_script);
1009 for (Tagged<SharedFunctionInfo> sfi = it.Next(); !sfi.is_null();
1010 sfi = it.Next()) {
1011 if (!sfi->HasBytecodeArray()) continue;
1012 Tagged<TrustedFixedArray> constants =
1013 sfi->GetBytecodeArray(isolate)->constant_pool();
1014 for (int i = 0; i < constants->length(); ++i) {
1015 if (!IsSharedFunctionInfo(constants->get(i))) continue;
1016 Tagged<SharedFunctionInfo> inner_sfi =
1017 Cast<SharedFunctionInfo>(constants->get(i));
1018 // See if there is a mapping from this function's start position to an
1019 // unchanged function's id.
1020 auto unchanged_it =
1021 start_position_to_unchanged_id.find(inner_sfi->StartPosition());
1022 if (unchanged_it == start_position_to_unchanged_id.end()) continue;
1023
1024 // Grab that function id from the new script's SFI list, which should have
1025 // already been updated in in the unchanged pass.
1026 Tagged<SharedFunctionInfo> old_unchanged_inner_sfi =
1028 new_script->infos()->get(unchanged_it->second).GetHeapObject());
1029 if (old_unchanged_inner_sfi == inner_sfi) continue;
1030 DCHECK_NE(old_unchanged_inner_sfi, inner_sfi);
1031 // Now some sanity checks. Make sure that the unchanged SFI has already
1032 // been processed and patched to be on the new script ...
1033 DCHECK_EQ(old_unchanged_inner_sfi->script(), *new_script);
1034 constants->set(i, old_unchanged_inner_sfi);
1035 }
1036 }
1037#ifdef DEBUG
1038 {
1039 // Check that all the functions in the new script are valid, that their
1040 // function literals match what is expected, and that start positions are
1041 // unique.
1043
1044 SharedFunctionInfo::ScriptIterator script_it(isolate, *new_script);
1045 std::set<int> start_positions;
1046 for (Tagged<SharedFunctionInfo> sfi = script_it.Next(); !sfi.is_null();
1047 sfi = script_it.Next()) {
1048 DCHECK_EQ(sfi->script(), *new_script);
1049 DCHECK_EQ(sfi->function_literal_id(), script_it.CurrentIndex());
1050 // Don't check the start position of the top-level function, as it can
1051 // overlap with a function in the script.
1052 if (sfi->is_toplevel()) {
1053 DCHECK_EQ(start_positions.find(sfi->StartPosition()),
1054 start_positions.end());
1055 start_positions.insert(sfi->StartPosition());
1056 }
1057
1058 if (!sfi->HasBytecodeArray()) continue;
1059 // Check that all the functions in this function's constant pool are also
1060 // on the new script, and that their id matches their index in the new
1061 // scripts function list.
1062 Tagged<TrustedFixedArray> constants =
1063 sfi->GetBytecodeArray(isolate)->constant_pool();
1064 for (int i = 0; i < constants->length(); ++i) {
1065 if (!IsSharedFunctionInfo(constants->get(i))) continue;
1066 Tagged<SharedFunctionInfo> inner_sfi =
1067 Cast<SharedFunctionInfo>(constants->get(i));
1068 DCHECK_EQ(inner_sfi->script(), *new_script);
1069 DCHECK_EQ(inner_sfi, new_script->infos()
1070 ->get(inner_sfi->function_literal_id())
1071 .GetHeapObject());
1072 }
1073 }
1074 }
1075#endif
1076
1077 int script_id = script->id();
1078 script->set_id(new_script->id());
1079 new_script->set_id(script_id);
1081 result->script = ToApiHandle<v8::debug::Script>(new_script);
1082}
1083
1085 Handle<String> s2,
1086 std::vector<SourceChangeRange>* diffs) {
1087 s1 = String::Flatten(isolate, s1);
1088 s2 = String::Flatten(isolate, s2);
1089
1090 LineEndsWrapper line_ends1(isolate, s1);
1091 LineEndsWrapper line_ends2(isolate, s2);
1092
1093 LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
1094 TokenizingLineArrayCompareOutput output(isolate, line_ends1, line_ends2, s1,
1095 s2, diffs);
1096
1097 NarrowDownInput(&input, &output);
1098
1099 Comparator::CalculateDifference(&input, &output);
1100}
1101
1102int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& diffs,
1103 int position) {
1104 auto it = std::lower_bound(diffs.begin(), diffs.end(), position,
1105 [](const SourceChangeRange& change, int position) {
1106 return change.end_position < position;
1107 });
1108 if (it != diffs.end() && position == it->end_position) {
1109 return it->new_end_position;
1110 }
1111 if (it == diffs.begin()) return position;
1112 DCHECK(it == diffs.end() || position <= it->start_position);
1113 it = std::prev(it);
1114 return position + (it->new_end_position - it->end_position);
1115}
1116} // namespace internal
1117} // namespace v8
Isolate * isolate_
friend Zone
Definition asm-types.cc:195
Local< v8::Message > Message() const
Definition api.cc:2829
bool HasCaught() const
Definition api.cc:2781
static v8::internal::DirectHandle< To > OpenDirectHandle(v8::Local< From > handle)
Definition api.h:279
static void CalculateDifference(Input *input, Output *result_writer)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< SharedFunctionInfo > CompileForLiveEdit(ParseInfo *parse_info, Handle< Script > script, MaybeDirectHandle< ScopeInfo > outer_scope_info, Isolate *isolate)
Definition compiler.cc:3241
int function_literal_id() const
Definition ast.h:2408
int start_position() const
Definition ast.cc:221
static V8_EXPORT_PRIVATE void EnsureFeedbackVector(Isolate *isolate, DirectHandle< JSFunction > function, IsCompiledScope *compiled_scope)
FeedbackVector eventually. Generally this shouldn't be used to get the.
static void PatchScript(Isolate *isolate, Handle< Script > script, Handle< String > source, bool preview, bool allow_top_frame_live_editing, debug::LiveEditResult *result)
Definition liveedit.cc:834
static int TranslatePosition(const std::vector< SourceChangeRange > &changed, int position)
Definition liveedit.cc:1102
static void CompareStrings(Isolate *isolate, Handle< String > a, Handle< String > b, std::vector< SourceChangeRange > *diffs)
Definition liveedit.cc:1084
V8_EXPORT_PRIVATE Tagged< SharedFunctionInfo > Next()
static void EnsureSourcePositionsAvailable(Isolate *isolate, DirectHandle< SharedFunctionInfo > shared_info)
static constexpr int ToInt(const Tagged< Object > object)
Definition smi.h:33
static V8_INLINE HandleType< String > Flatten(Isolate *isolate, HandleType< T > string, AllocationType allocation=AllocationType::kYoung)
static Handle< FixedArray > CalculateLineEnds(IsolateT *isolate, DirectHandle< String > string, bool include_ending_line)
Definition string.cc:1194
V8_INLINE constexpr bool is_null() const
Definition tagged.h:502
static UnoptimizedCompileFlags ForScriptCompile(Isolate *isolate, Tagged< Script > script)
Definition parse-info.cc:69
T * insert(const T *pos, It first, It last)
const MapRef map_
int32_t offset
SharedFunctionInfoRef shared
ZoneVector< RpoNumber > & result
Comparator::Output * output_
int subrange_offset2_
Definition liveedit.cc:221
int new_start_position
Definition liveedit.cc:342
std::vector< Handle< JSGeneratorObject > > running_generators
Definition liveedit.cc:560
FunctionLiteral * literal
Definition liveedit.cc:294
int len2_
Definition liveedit.cc:121
int offset1_
Definition liveedit.cc:117
std::vector< FunctionLiteral * > * literals_
Definition liveedit.cc:513
int position
Definition liveedit.cc:290
int len1_
Definition liveedit.cc:118
LineEndsWrapper line_ends2_
Definition liveedit.cc:219
static const int CHUNK_LEN_LIMIT
Definition liveedit.cc:275
int pos_diff
Definition liveedit.cc:295
std::vector< Handle< JSFunction > > js_functions
Definition liveedit.cc:559
int subrange_offset1_
Definition liveedit.cc:220
int string_len_
Definition liveedit.cc:170
StackPosition stack_position
Definition liveedit.cc:565
LineEndsWrapper line_ends1_
Definition liveedit.cc:218
int new_end_position
Definition liveedit.cc:343
Handle< String > s2_
Definition liveedit.cc:119
FunctionLiteral * outer_literal
Definition liveedit.cc:345
bool has_changes
Definition liveedit.cc:344
Handle< String > s1_
Definition liveedit.cc:116
int subrange_len1_
Definition liveedit.cc:222
int offset2_
Definition liveedit.cc:120
Handle< FixedArray > ends_array_
Definition liveedit.cc:169
int subrange_len2_
Definition liveedit.cc:223
#define LOG_CODE_EVENT(isolate, Call)
Definition log.h:83
SnapshotTable< OpIndex, VariableData >::Key Variable
Definition operations.h:82
bool ParseProgram(ParseInfo *info, DirectHandle< Script > script, MaybeDirectHandle< ScopeInfo > maybe_outer_scope_info, Isolate *isolate, ReportStatisticsMode mode)
Definition parsing.cc:39
V8_INLINE IndirectHandle< T > handle(Tagged< T > object, Isolate *isolate)
Definition handles-inl.h:72
constexpr NullMaybeHandleType kNullMaybeHandle
constexpr int kNoSourcePosition
Definition globals.h:850
Tagged(T object) -> Tagged< T >
kInterpreterTrampolineOffset Tagged< HeapObject >
V8_INLINE DirectHandle< T > direct_handle(Tagged< T > object, Isolate *isolate)
bool IsModule(FunctionKind kind)
Tagged< MaybeWeak< T > > MakeWeak(Tagged< T > value)
Definition tagged.h:893
constexpr int kFunctionLiteralIdTopLevel
Definition globals.h:2767
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
static constexpr ReleaseStoreTag kReleaseStore
Definition globals.h:2910
Local< T > Handle
v8::Local< T > ToApiHandle(v8::internal::DirectHandle< v8::internal::Object > obj)
Definition api.h:297
BodyGen * gen
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define ZONE_NAME
Definition zone.h:22