// Copyright 2012 the V8 project authors. All rights reserved.
|
// Use of this source code is governed by a BSD-style license that can be
|
// found in the LICENSE file.
|
|
#include "src/debug/liveedit.h"
|
|
#include "src/api-inl.h"
|
#include "src/ast/ast-traversal-visitor.h"
|
#include "src/ast/ast.h"
|
#include "src/ast/scopes.h"
|
#include "src/compilation-cache.h"
|
#include "src/compiler.h"
|
#include "src/debug/debug-interface.h"
|
#include "src/debug/debug.h"
|
#include "src/frames-inl.h"
|
#include "src/isolate-inl.h"
|
#include "src/messages.h"
|
#include "src/objects-inl.h"
|
#include "src/objects/hash-table-inl.h"
|
#include "src/objects/js-generator-inl.h"
|
#include "src/parsing/parse-info.h"
|
#include "src/parsing/parsing.h"
|
#include "src/source-position-table.h"
|
#include "src/v8.h"
|
|
namespace v8 {
|
namespace internal {
|
namespace {
|
// A general-purpose comparator between 2 arrays.
|
class Comparator {
|
public:
|
// Holds 2 arrays of some elements allowing to compare any pair of
|
// element from the first array and element from the second array.
|
class Input {
|
public:
|
virtual int GetLength1() = 0;
|
virtual int GetLength2() = 0;
|
virtual bool Equals(int index1, int index2) = 0;
|
|
protected:
|
virtual ~Input() = default;
|
};
|
|
// Receives compare result as a series of chunks.
|
class Output {
|
public:
|
// Puts another chunk in result list. Note that technically speaking
|
// only 3 arguments actually needed with 4th being derivable.
|
virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0;
|
|
protected:
|
virtual ~Output() = default;
|
};
|
|
// Finds the difference between 2 arrays of elements.
|
static void CalculateDifference(Input* input, Output* result_writer);
|
};
|
|
// A simple implementation of dynamic programming algorithm. It solves
|
// the problem of finding the difference of 2 arrays. It uses a table of results
|
// of subproblems. Each cell contains a number together with 2-bit flag
|
// that helps building the chunk list.
|
class Differencer {
|
public:
|
explicit Differencer(Comparator::Input* input)
|
: input_(input), len1_(input->GetLength1()), len2_(input->GetLength2()) {
|
buffer_ = NewArray<int>(len1_ * len2_);
|
}
|
~Differencer() {
|
DeleteArray(buffer_);
|
}
|
|
void Initialize() {
|
int array_size = len1_ * len2_;
|
for (int i = 0; i < array_size; i++) {
|
buffer_[i] = kEmptyCellValue;
|
}
|
}
|
|
// Makes sure that result for the full problem is calculated and stored
|
// in the table together with flags showing a path through subproblems.
|
void FillTable() {
|
CompareUpToTail(0, 0);
|
}
|
|
void SaveResult(Comparator::Output* chunk_writer) {
|
ResultWriter writer(chunk_writer);
|
|
int pos1 = 0;
|
int pos2 = 0;
|
while (true) {
|
if (pos1 < len1_) {
|
if (pos2 < len2_) {
|
Direction dir = get_direction(pos1, pos2);
|
switch (dir) {
|
case EQ:
|
writer.eq();
|
pos1++;
|
pos2++;
|
break;
|
case SKIP1:
|
writer.skip1(1);
|
pos1++;
|
break;
|
case SKIP2:
|
case SKIP_ANY:
|
writer.skip2(1);
|
pos2++;
|
break;
|
default:
|
UNREACHABLE();
|
}
|
} else {
|
writer.skip1(len1_ - pos1);
|
break;
|
}
|
} else {
|
if (len2_ != pos2) {
|
writer.skip2(len2_ - pos2);
|
}
|
break;
|
}
|
}
|
writer.close();
|
}
|
|
private:
|
Comparator::Input* input_;
|
int* buffer_;
|
int len1_;
|
int len2_;
|
|
enum Direction {
|
EQ = 0,
|
SKIP1,
|
SKIP2,
|
SKIP_ANY,
|
|
MAX_DIRECTION_FLAG_VALUE = SKIP_ANY
|
};
|
|
// Computes result for a subtask and optionally caches it in the buffer table.
|
// All results values are shifted to make space for flags in the lower bits.
|
int CompareUpToTail(int pos1, int pos2) {
|
if (pos1 < len1_) {
|
if (pos2 < len2_) {
|
int cached_res = get_value4(pos1, pos2);
|
if (cached_res == kEmptyCellValue) {
|
Direction dir;
|
int res;
|
if (input_->Equals(pos1, pos2)) {
|
res = CompareUpToTail(pos1 + 1, pos2 + 1);
|
dir = EQ;
|
} else {
|
int res1 = CompareUpToTail(pos1 + 1, pos2) +
|
(1 << kDirectionSizeBits);
|
int res2 = CompareUpToTail(pos1, pos2 + 1) +
|
(1 << kDirectionSizeBits);
|
if (res1 == res2) {
|
res = res1;
|
dir = SKIP_ANY;
|
} else if (res1 < res2) {
|
res = res1;
|
dir = SKIP1;
|
} else {
|
res = res2;
|
dir = SKIP2;
|
}
|
}
|
set_value4_and_dir(pos1, pos2, res, dir);
|
cached_res = res;
|
}
|
return cached_res;
|
} else {
|
return (len1_ - pos1) << kDirectionSizeBits;
|
}
|
} else {
|
return (len2_ - pos2) << kDirectionSizeBits;
|
}
|
}
|
|
inline int& get_cell(int i1, int i2) {
|
return buffer_[i1 + i2 * len1_];
|
}
|
|
// Each cell keeps a value plus direction. Value is multiplied by 4.
|
void set_value4_and_dir(int i1, int i2, int value4, Direction dir) {
|
DCHECK_EQ(0, value4 & kDirectionMask);
|
get_cell(i1, i2) = value4 | dir;
|
}
|
|
int get_value4(int i1, int i2) {
|
return get_cell(i1, i2) & (kMaxUInt32 ^ kDirectionMask);
|
}
|
Direction get_direction(int i1, int i2) {
|
return static_cast<Direction>(get_cell(i1, i2) & kDirectionMask);
|
}
|
|
static const int kDirectionSizeBits = 2;
|
static const int kDirectionMask = (1 << kDirectionSizeBits) - 1;
|
static const int kEmptyCellValue = ~0u << kDirectionSizeBits;
|
|
// This method only holds static assert statement (unfortunately you cannot
|
// place one in class scope).
|
void StaticAssertHolder() {
|
STATIC_ASSERT(MAX_DIRECTION_FLAG_VALUE < (1 << kDirectionSizeBits));
|
}
|
|
class ResultWriter {
|
public:
|
explicit ResultWriter(Comparator::Output* chunk_writer)
|
: chunk_writer_(chunk_writer), pos1_(0), pos2_(0),
|
pos1_begin_(-1), pos2_begin_(-1), has_open_chunk_(false) {
|
}
|
void eq() {
|
FlushChunk();
|
pos1_++;
|
pos2_++;
|
}
|
void skip1(int len1) {
|
StartChunk();
|
pos1_ += len1;
|
}
|
void skip2(int len2) {
|
StartChunk();
|
pos2_ += len2;
|
}
|
void close() {
|
FlushChunk();
|
}
|
|
private:
|
Comparator::Output* chunk_writer_;
|
int pos1_;
|
int pos2_;
|
int pos1_begin_;
|
int pos2_begin_;
|
bool has_open_chunk_;
|
|
void StartChunk() {
|
if (!has_open_chunk_) {
|
pos1_begin_ = pos1_;
|
pos2_begin_ = pos2_;
|
has_open_chunk_ = true;
|
}
|
}
|
|
void FlushChunk() {
|
if (has_open_chunk_) {
|
chunk_writer_->AddChunk(pos1_begin_, pos2_begin_,
|
pos1_ - pos1_begin_, pos2_ - pos2_begin_);
|
has_open_chunk_ = false;
|
}
|
}
|
};
|
};
|
|
void Comparator::CalculateDifference(Comparator::Input* input,
|
Comparator::Output* result_writer) {
|
Differencer differencer(input);
|
differencer.Initialize();
|
differencer.FillTable();
|
differencer.SaveResult(result_writer);
|
}
|
|
bool CompareSubstrings(Handle<String> s1, int pos1, Handle<String> s2, int pos2,
|
int len) {
|
for (int i = 0; i < len; i++) {
|
if (s1->Get(i + pos1) != s2->Get(i + pos2)) return false;
|
}
|
return true;
|
}
|
|
// Additional to Input interface. Lets switch Input range to subrange.
|
// More elegant way would be to wrap one Input as another Input object
|
// and translate positions there, but that would cost us additional virtual
|
// call per comparison.
|
class SubrangableInput : public Comparator::Input {
|
public:
|
virtual void SetSubrange1(int offset, int len) = 0;
|
virtual void SetSubrange2(int offset, int len) = 0;
|
};
|
|
|
class SubrangableOutput : public Comparator::Output {
|
public:
|
virtual void SetSubrange1(int offset, int len) = 0;
|
virtual void SetSubrange2(int offset, int len) = 0;
|
};
|
|
// Finds common prefix and suffix in input. This parts shouldn't take space in
|
// linear programming table. Enable subranging in input and output.
|
void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) {
|
const int len1 = input->GetLength1();
|
const int len2 = input->GetLength2();
|
|
int common_prefix_len;
|
int common_suffix_len;
|
|
{
|
common_prefix_len = 0;
|
int prefix_limit = std::min(len1, len2);
|
while (common_prefix_len < prefix_limit &&
|
input->Equals(common_prefix_len, common_prefix_len)) {
|
common_prefix_len++;
|
}
|
|
common_suffix_len = 0;
|
int suffix_limit =
|
std::min(len1 - common_prefix_len, len2 - common_prefix_len);
|
|
while (common_suffix_len < suffix_limit &&
|
input->Equals(len1 - common_suffix_len - 1,
|
len2 - common_suffix_len - 1)) {
|
common_suffix_len++;
|
}
|
}
|
|
if (common_prefix_len > 0 || common_suffix_len > 0) {
|
int new_len1 = len1 - common_suffix_len - common_prefix_len;
|
int new_len2 = len2 - common_suffix_len - common_prefix_len;
|
|
input->SetSubrange1(common_prefix_len, new_len1);
|
input->SetSubrange2(common_prefix_len, new_len2);
|
|
output->SetSubrange1(common_prefix_len, new_len1);
|
output->SetSubrange2(common_prefix_len, new_len2);
|
}
|
}
|
|
// Represents 2 strings as 2 arrays of tokens.
|
// TODO(LiveEdit): Currently it's actually an array of charactres.
|
// Make array of tokens instead.
|
class TokensCompareInput : public Comparator::Input {
|
public:
|
TokensCompareInput(Handle<String> s1, int offset1, int len1,
|
Handle<String> s2, int offset2, int len2)
|
: s1_(s1), offset1_(offset1), len1_(len1),
|
s2_(s2), offset2_(offset2), len2_(len2) {
|
}
|
int GetLength1() override { return len1_; }
|
int GetLength2() override { return len2_; }
|
bool Equals(int index1, int index2) override {
|
return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2);
|
}
|
|
private:
|
Handle<String> s1_;
|
int offset1_;
|
int len1_;
|
Handle<String> s2_;
|
int offset2_;
|
int len2_;
|
};
|
|
// Stores compare result in std::vector. Converts substring positions
|
// to absolute positions.
|
class TokensCompareOutput : public Comparator::Output {
|
public:
|
TokensCompareOutput(int offset1, int offset2,
|
std::vector<SourceChangeRange>* output)
|
: output_(output), offset1_(offset1), offset2_(offset2) {}
|
|
void AddChunk(int pos1, int pos2, int len1, int len2) override {
|
output_->emplace_back(
|
SourceChangeRange{pos1 + offset1_, pos1 + len1 + offset1_,
|
pos2 + offset2_, pos2 + offset2_ + len2});
|
}
|
|
private:
|
std::vector<SourceChangeRange>* output_;
|
int offset1_;
|
int offset2_;
|
};
|
|
// Wraps raw n-elements line_ends array as a list of n+1 lines. The last line
|
// never has terminating new line character.
|
class LineEndsWrapper {
|
public:
|
explicit LineEndsWrapper(Isolate* isolate, Handle<String> string)
|
: ends_array_(String::CalculateLineEnds(isolate, string, false)),
|
string_len_(string->length()) {}
|
int length() {
|
return ends_array_->length() + 1;
|
}
|
// Returns start for any line including start of the imaginary line after
|
// the last line.
|
int GetLineStart(int index) { return index == 0 ? 0 : GetLineEnd(index - 1); }
|
int GetLineEnd(int index) {
|
if (index == ends_array_->length()) {
|
// End of the last line is always an end of the whole string.
|
// If the string ends with a new line character, the last line is an
|
// empty string after this character.
|
return string_len_;
|
} else {
|
return GetPosAfterNewLine(index);
|
}
|
}
|
|
private:
|
Handle<FixedArray> ends_array_;
|
int string_len_;
|
|
int GetPosAfterNewLine(int index) {
|
return Smi::ToInt(ends_array_->get(index)) + 1;
|
}
|
};
|
|
// Represents 2 strings as 2 arrays of lines.
|
class LineArrayCompareInput : public SubrangableInput {
|
public:
|
LineArrayCompareInput(Handle<String> s1, Handle<String> s2,
|
LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
|
: s1_(s1), s2_(s2), line_ends1_(line_ends1),
|
line_ends2_(line_ends2),
|
subrange_offset1_(0), subrange_offset2_(0),
|
subrange_len1_(line_ends1_.length()),
|
subrange_len2_(line_ends2_.length()) {
|
}
|
int GetLength1() override { return subrange_len1_; }
|
int GetLength2() override { return subrange_len2_; }
|
bool Equals(int index1, int index2) override {
|
index1 += subrange_offset1_;
|
index2 += subrange_offset2_;
|
|
int line_start1 = line_ends1_.GetLineStart(index1);
|
int line_start2 = line_ends2_.GetLineStart(index2);
|
int line_end1 = line_ends1_.GetLineEnd(index1);
|
int line_end2 = line_ends2_.GetLineEnd(index2);
|
int len1 = line_end1 - line_start1;
|
int len2 = line_end2 - line_start2;
|
if (len1 != len2) {
|
return false;
|
}
|
return CompareSubstrings(s1_, line_start1, s2_, line_start2,
|
len1);
|
}
|
void SetSubrange1(int offset, int len) override {
|
subrange_offset1_ = offset;
|
subrange_len1_ = len;
|
}
|
void SetSubrange2(int offset, int len) override {
|
subrange_offset2_ = offset;
|
subrange_len2_ = len;
|
}
|
|
private:
|
Handle<String> s1_;
|
Handle<String> s2_;
|
LineEndsWrapper line_ends1_;
|
LineEndsWrapper line_ends2_;
|
int subrange_offset1_;
|
int subrange_offset2_;
|
int subrange_len1_;
|
int subrange_len2_;
|
};
|
|
// Stores compare result in std::vector. For each chunk tries to conduct
|
// a fine-grained nested diff token-wise.
|
class TokenizingLineArrayCompareOutput : public SubrangableOutput {
|
public:
|
TokenizingLineArrayCompareOutput(Isolate* isolate, LineEndsWrapper line_ends1,
|
LineEndsWrapper line_ends2,
|
Handle<String> s1, Handle<String> s2,
|
std::vector<SourceChangeRange>* output)
|
: isolate_(isolate),
|
line_ends1_(line_ends1),
|
line_ends2_(line_ends2),
|
s1_(s1),
|
s2_(s2),
|
subrange_offset1_(0),
|
subrange_offset2_(0),
|
output_(output) {}
|
|
void AddChunk(int line_pos1, int line_pos2, int line_len1,
|
int line_len2) override {
|
line_pos1 += subrange_offset1_;
|
line_pos2 += subrange_offset2_;
|
|
int char_pos1 = line_ends1_.GetLineStart(line_pos1);
|
int char_pos2 = line_ends2_.GetLineStart(line_pos2);
|
int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1;
|
int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2;
|
|
if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) {
|
// Chunk is small enough to conduct a nested token-level diff.
|
HandleScope subTaskScope(isolate_);
|
|
TokensCompareInput tokens_input(s1_, char_pos1, char_len1,
|
s2_, char_pos2, char_len2);
|
TokensCompareOutput tokens_output(char_pos1, char_pos2, output_);
|
|
Comparator::CalculateDifference(&tokens_input, &tokens_output);
|
} else {
|
output_->emplace_back(SourceChangeRange{
|
char_pos1, char_pos1 + char_len1, char_pos2, char_pos2 + char_len2});
|
}
|
}
|
void SetSubrange1(int offset, int len) override {
|
subrange_offset1_ = offset;
|
}
|
void SetSubrange2(int offset, int len) override {
|
subrange_offset2_ = offset;
|
}
|
|
private:
|
static const int CHUNK_LEN_LIMIT = 800;
|
|
Isolate* isolate_;
|
LineEndsWrapper line_ends1_;
|
LineEndsWrapper line_ends2_;
|
Handle<String> s1_;
|
Handle<String> s2_;
|
int subrange_offset1_;
|
int subrange_offset2_;
|
std::vector<SourceChangeRange>* output_;
|
};
|
|
struct SourcePositionEvent {
|
enum Type { LITERAL_STARTS, LITERAL_ENDS, DIFF_STARTS, DIFF_ENDS };
|
|
int position;
|
Type type;
|
|
union {
|
FunctionLiteral* literal;
|
int pos_diff;
|
};
|
|
SourcePositionEvent(FunctionLiteral* literal, bool is_start)
|
: position(is_start ? literal->start_position()
|
: literal->end_position()),
|
type(is_start ? LITERAL_STARTS : LITERAL_ENDS),
|
literal(literal) {}
|
SourcePositionEvent(const SourceChangeRange& change, bool is_start)
|
: position(is_start ? change.start_position : change.end_position),
|
type(is_start ? DIFF_STARTS : DIFF_ENDS),
|
pos_diff((change.new_end_position - change.new_start_position) -
|
(change.end_position - change.start_position)) {}
|
|
static bool LessThan(const SourcePositionEvent& a,
|
const SourcePositionEvent& b) {
|
if (a.position != b.position) return a.position < b.position;
|
if (a.type != b.type) return a.type < b.type;
|
if (a.type == LITERAL_STARTS && b.type == LITERAL_STARTS) {
|
// If the literals start in the same position, we want the one with the
|
// furthest (i.e. largest) end position to be first.
|
if (a.literal->end_position() != b.literal->end_position()) {
|
return a.literal->end_position() > b.literal->end_position();
|
}
|
// If they also end in the same position, we want the first in order of
|
// literal ids to be first.
|
return a.literal->function_literal_id() <
|
b.literal->function_literal_id();
|
} else if (a.type == LITERAL_ENDS && b.type == LITERAL_ENDS) {
|
// If the literals end in the same position, we want the one with the
|
// nearest (i.e. largest) start position to be first.
|
if (a.literal->start_position() != b.literal->start_position()) {
|
return a.literal->start_position() > b.literal->start_position();
|
}
|
// If they also end in the same position, we want the last in order of
|
// literal ids to be first.
|
return a.literal->function_literal_id() >
|
b.literal->function_literal_id();
|
} else {
|
return a.pos_diff < b.pos_diff;
|
}
|
}
|
};
|
|
struct FunctionLiteralChange {
|
// If any of start/end position is kNoSourcePosition, this literal is
|
// considered damaged and will not be mapped and edited at all.
|
int new_start_position;
|
int new_end_position;
|
bool has_changes;
|
FunctionLiteral* outer_literal;
|
|
explicit FunctionLiteralChange(int new_start_position, FunctionLiteral* outer)
|
: new_start_position(new_start_position),
|
new_end_position(kNoSourcePosition),
|
has_changes(false),
|
outer_literal(outer) {}
|
};
|
|
using FunctionLiteralChanges =
|
std::unordered_map<FunctionLiteral*, FunctionLiteralChange>;
|
void CalculateFunctionLiteralChanges(
|
const std::vector<FunctionLiteral*>& literals,
|
const std::vector<SourceChangeRange>& diffs,
|
FunctionLiteralChanges* result) {
|
std::vector<SourcePositionEvent> events;
|
events.reserve(literals.size() * 2 + diffs.size() * 2);
|
for (FunctionLiteral* literal : literals) {
|
events.emplace_back(literal, true);
|
events.emplace_back(literal, false);
|
}
|
for (const SourceChangeRange& diff : diffs) {
|
events.emplace_back(diff, true);
|
events.emplace_back(diff, false);
|
}
|
std::sort(events.begin(), events.end(), SourcePositionEvent::LessThan);
|
bool inside_diff = false;
|
int delta = 0;
|
std::stack<std::pair<FunctionLiteral*, FunctionLiteralChange>> literal_stack;
|
for (const SourcePositionEvent& event : events) {
|
switch (event.type) {
|
case SourcePositionEvent::DIFF_ENDS:
|
DCHECK(inside_diff);
|
inside_diff = false;
|
delta += event.pos_diff;
|
break;
|
case SourcePositionEvent::LITERAL_ENDS: {
|
DCHECK_EQ(literal_stack.top().first, event.literal);
|
FunctionLiteralChange& change = literal_stack.top().second;
|
change.new_end_position = inside_diff
|
? kNoSourcePosition
|
: event.literal->end_position() + delta;
|
result->insert(literal_stack.top());
|
literal_stack.pop();
|
break;
|
}
|
case SourcePositionEvent::LITERAL_STARTS:
|
literal_stack.push(std::make_pair(
|
event.literal,
|
FunctionLiteralChange(
|
inside_diff ? kNoSourcePosition
|
: event.literal->start_position() + delta,
|
literal_stack.empty() ? nullptr : literal_stack.top().first)));
|
break;
|
case SourcePositionEvent::DIFF_STARTS:
|
DCHECK(!inside_diff);
|
inside_diff = true;
|
if (!literal_stack.empty()) {
|
// Note that outer literal has not necessarily changed, unless the
|
// diff goes past the end of this literal. In this case, we'll mark
|
// this function as damaged and parent as changed later in
|
// MapLiterals.
|
literal_stack.top().second.has_changes = true;
|
}
|
break;
|
}
|
}
|
}
|
|
// Function which has not changed itself, but if any variable in its
|
// outer context has been added/removed, we must consider this function
|
// as damaged and not update references to it.
|
// This is because old compiled function has hardcoded references to
|
// it's outer context.
|
bool HasChangedScope(FunctionLiteral* a, FunctionLiteral* b) {
|
Scope* scope_a = a->scope()->outer_scope();
|
Scope* scope_b = b->scope()->outer_scope();
|
while (scope_a && scope_b) {
|
std::unordered_map<int, Handle<String>> vars;
|
for (Variable* var : *scope_a->locals()) {
|
if (!var->IsContextSlot()) continue;
|
vars[var->index()] = var->name();
|
}
|
for (Variable* var : *scope_b->locals()) {
|
if (!var->IsContextSlot()) continue;
|
auto it = vars.find(var->index());
|
if (it == vars.end()) return true;
|
if (*it->second != *var->name()) return true;
|
}
|
scope_a = scope_a->outer_scope();
|
scope_b = scope_b->outer_scope();
|
}
|
return scope_a != scope_b;
|
}
|
|
enum ChangeState { UNCHANGED, CHANGED, DAMAGED };
|
|
using LiteralMap = std::unordered_map<FunctionLiteral*, FunctionLiteral*>;
|
void MapLiterals(const FunctionLiteralChanges& changes,
|
const std::vector<FunctionLiteral*>& new_literals,
|
LiteralMap* unchanged, LiteralMap* changed) {
|
// Track the top-level script function separately as it can overlap fully with
|
// another function, e.g. the script "()=>42".
|
const std::pair<int, int> kTopLevelMarker = std::make_pair(-1, -1);
|
std::map<std::pair<int, int>, FunctionLiteral*> position_to_new_literal;
|
for (FunctionLiteral* literal : new_literals) {
|
DCHECK(literal->start_position() != kNoSourcePosition);
|
DCHECK(literal->end_position() != kNoSourcePosition);
|
std::pair<int, int> key =
|
literal->function_literal_id() == FunctionLiteral::kIdTypeTopLevel
|
? kTopLevelMarker
|
: std::make_pair(literal->start_position(),
|
literal->end_position());
|
// Make sure there are no duplicate keys.
|
DCHECK_EQ(position_to_new_literal.find(key), position_to_new_literal.end());
|
position_to_new_literal[key] = literal;
|
}
|
LiteralMap mappings;
|
std::unordered_map<FunctionLiteral*, ChangeState> change_state;
|
for (const auto& change_pair : changes) {
|
FunctionLiteral* literal = change_pair.first;
|
const FunctionLiteralChange& change = change_pair.second;
|
std::pair<int, int> key =
|
literal->function_literal_id() == FunctionLiteral::kIdTypeTopLevel
|
? kTopLevelMarker
|
: std::make_pair(change.new_start_position,
|
change.new_end_position);
|
auto it = position_to_new_literal.find(key);
|
if (it == position_to_new_literal.end() ||
|
HasChangedScope(literal, it->second)) {
|
change_state[literal] = ChangeState::DAMAGED;
|
if (!change.outer_literal) continue;
|
if (change_state[change.outer_literal] != ChangeState::DAMAGED) {
|
change_state[change.outer_literal] = ChangeState::CHANGED;
|
}
|
} else {
|
mappings[literal] = it->second;
|
if (change_state.find(literal) == change_state.end()) {
|
change_state[literal] =
|
change.has_changes ? ChangeState::CHANGED : ChangeState::UNCHANGED;
|
}
|
}
|
}
|
for (const auto& mapping : mappings) {
|
if (change_state[mapping.first] == ChangeState::UNCHANGED) {
|
(*unchanged)[mapping.first] = mapping.second;
|
} else if (change_state[mapping.first] == ChangeState::CHANGED) {
|
(*changed)[mapping.first] = mapping.second;
|
}
|
}
|
}
|
|
class CollectFunctionLiterals final
|
: public AstTraversalVisitor<CollectFunctionLiterals> {
|
public:
|
CollectFunctionLiterals(Isolate* isolate, AstNode* root)
|
: AstTraversalVisitor<CollectFunctionLiterals>(isolate, root) {}
|
void VisitFunctionLiteral(FunctionLiteral* lit) {
|
AstTraversalVisitor::VisitFunctionLiteral(lit);
|
literals_->push_back(lit);
|
}
|
void Run(std::vector<FunctionLiteral*>* literals) {
|
literals_ = literals;
|
AstTraversalVisitor::Run();
|
literals_ = nullptr;
|
}
|
|
private:
|
std::vector<FunctionLiteral*>* literals_;
|
};
|
|
bool ParseScript(Isolate* isolate, ParseInfo* parse_info, bool compile_as_well,
|
std::vector<FunctionLiteral*>* literals,
|
debug::LiveEditResult* result) {
|
parse_info->set_eager();
|
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
|
Handle<SharedFunctionInfo> shared;
|
bool success = false;
|
if (compile_as_well) {
|
success =
|
Compiler::CompileForLiveEdit(parse_info, isolate).ToHandle(&shared);
|
} else {
|
success = parsing::ParseProgram(parse_info, isolate);
|
if (success) {
|
success = Compiler::Analyze(parse_info);
|
parse_info->ast_value_factory()->Internalize(isolate);
|
}
|
}
|
if (!success) {
|
isolate->OptionalRescheduleException(false);
|
DCHECK(try_catch.HasCaught());
|
result->message = try_catch.Message()->Get();
|
auto self = Utils::OpenHandle(*try_catch.Message());
|
auto msg = i::Handle<i::JSMessageObject>::cast(self);
|
result->line_number = msg->GetLineNumber();
|
result->column_number = msg->GetColumnNumber();
|
result->status = debug::LiveEditResult::COMPILE_ERROR;
|
return false;
|
}
|
CollectFunctionLiterals(isolate, parse_info->literal()).Run(literals);
|
return true;
|
}
|
|
struct FunctionData {
|
FunctionData(FunctionLiteral* literal, bool should_restart)
|
: literal(literal),
|
stack_position(NOT_ON_STACK),
|
should_restart(should_restart) {}
|
|
FunctionLiteral* literal;
|
MaybeHandle<SharedFunctionInfo> shared;
|
std::vector<Handle<JSFunction>> js_functions;
|
std::vector<Handle<JSGeneratorObject>> running_generators;
|
// In case of multiple functions with different stack position, the latest
|
// one (in the order below) is used, since it is the most restrictive.
|
// This is important only for functions to be restarted.
|
enum StackPosition {
|
NOT_ON_STACK,
|
ABOVE_BREAK_FRAME,
|
PATCHABLE,
|
BELOW_NON_DROPPABLE_FRAME,
|
ARCHIVED_THREAD,
|
};
|
StackPosition stack_position;
|
bool should_restart;
|
};
|
|
class FunctionDataMap : public ThreadVisitor {
|
public:
|
void AddInterestingLiteral(int script_id, FunctionLiteral* literal,
|
bool should_restart) {
|
map_.emplace(GetFuncId(script_id, literal),
|
FunctionData{literal, should_restart});
|
}
|
|
bool Lookup(SharedFunctionInfo* sfi, FunctionData** data) {
|
int start_position = sfi->StartPosition();
|
if (!sfi->script()->IsScript() || start_position == -1) {
|
return false;
|
}
|
Script* script = Script::cast(sfi->script());
|
return Lookup(GetFuncId(script->id(), sfi), data);
|
}
|
|
bool Lookup(Handle<Script> script, FunctionLiteral* literal,
|
FunctionData** data) {
|
return Lookup(GetFuncId(script->id(), literal), data);
|
}
|
|
void Fill(Isolate* isolate, Address* restart_frame_fp) {
|
{
|
HeapIterator iterator(isolate->heap(), HeapIterator::kFilterUnreachable);
|
while (HeapObject* obj = iterator.next()) {
|
if (obj->IsSharedFunctionInfo()) {
|
SharedFunctionInfo* sfi = SharedFunctionInfo::cast(obj);
|
FunctionData* data = nullptr;
|
if (!Lookup(sfi, &data)) continue;
|
data->shared = handle(sfi, isolate);
|
} else if (obj->IsJSFunction()) {
|
JSFunction* js_function = JSFunction::cast(obj);
|
SharedFunctionInfo* sfi = js_function->shared();
|
FunctionData* data = nullptr;
|
if (!Lookup(sfi, &data)) continue;
|
data->js_functions.emplace_back(js_function, isolate);
|
} else if (obj->IsJSGeneratorObject()) {
|
JSGeneratorObject* gen = JSGeneratorObject::cast(obj);
|
if (gen->is_closed()) continue;
|
SharedFunctionInfo* sfi = gen->function()->shared();
|
FunctionData* data = nullptr;
|
if (!Lookup(sfi, &data)) continue;
|
data->running_generators.emplace_back(gen, isolate);
|
}
|
}
|
}
|
FunctionData::StackPosition stack_position =
|
isolate->debug()->break_frame_id() == StackFrame::NO_ID
|
? FunctionData::PATCHABLE
|
: FunctionData::ABOVE_BREAK_FRAME;
|
for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
|
StackFrame* frame = it.frame();
|
if (stack_position == FunctionData::ABOVE_BREAK_FRAME) {
|
if (frame->id() == isolate->debug()->break_frame_id()) {
|
stack_position = FunctionData::PATCHABLE;
|
}
|
}
|
if (stack_position == FunctionData::PATCHABLE &&
|
(frame->is_exit() || frame->is_builtin_exit())) {
|
stack_position = FunctionData::BELOW_NON_DROPPABLE_FRAME;
|
continue;
|
}
|
if (!frame->is_java_script()) continue;
|
std::vector<Handle<SharedFunctionInfo>> sfis;
|
JavaScriptFrame::cast(frame)->GetFunctions(&sfis);
|
for (auto& sfi : sfis) {
|
if (stack_position == FunctionData::PATCHABLE &&
|
IsResumableFunction(sfi->kind())) {
|
stack_position = FunctionData::BELOW_NON_DROPPABLE_FRAME;
|
}
|
FunctionData* data = nullptr;
|
if (!Lookup(*sfi, &data)) continue;
|
if (!data->should_restart) continue;
|
data->stack_position = stack_position;
|
*restart_frame_fp = frame->fp();
|
}
|
}
|
|
isolate->thread_manager()->IterateArchivedThreads(this);
|
}
|
|
private:
|
// Unique id for a function: script_id + start_position, where start_position
|
// is special cased to -1 for top-level so that it does not overlap with a
|
// function whose start position is 0.
|
using FuncId = std::pair<int, int>;
|
|
FuncId GetFuncId(int script_id, FunctionLiteral* literal) {
|
int start_position = literal->start_position();
|
if (literal->function_literal_id() == 0) {
|
// This is the top-level script function literal, so special case its
|
// start position
|
DCHECK_EQ(start_position, 0);
|
start_position = -1;
|
}
|
return FuncId(script_id, start_position);
|
}
|
|
FuncId GetFuncId(int script_id, SharedFunctionInfo* sfi) {
|
DCHECK_EQ(script_id, Script::cast(sfi->script())->id());
|
int start_position = sfi->StartPosition();
|
DCHECK_NE(start_position, -1);
|
if (sfi->is_toplevel()) {
|
// This is the top-level function, so special case its start position
|
DCHECK_EQ(start_position, 0);
|
start_position = -1;
|
}
|
return FuncId(script_id, start_position);
|
}
|
|
bool Lookup(FuncId id, FunctionData** data) {
|
auto it = map_.find(id);
|
if (it == map_.end()) return false;
|
*data = &it->second;
|
return true;
|
}
|
|
void VisitThread(Isolate* isolate, ThreadLocalTop* top) override {
|
for (JavaScriptFrameIterator it(isolate, top); !it.done(); it.Advance()) {
|
std::vector<Handle<SharedFunctionInfo>> sfis;
|
it.frame()->GetFunctions(&sfis);
|
for (auto& sfi : sfis) {
|
FunctionData* data = nullptr;
|
if (!Lookup(*sfi, &data)) continue;
|
data->stack_position = FunctionData::ARCHIVED_THREAD;
|
}
|
}
|
}
|
|
std::map<FuncId, FunctionData> map_;
|
};
|
|
bool CanPatchScript(const LiteralMap& changed, Handle<Script> script,
|
Handle<Script> new_script,
|
FunctionDataMap& function_data_map,
|
debug::LiveEditResult* result) {
|
debug::LiveEditResult::Status status = debug::LiveEditResult::OK;
|
for (const auto& mapping : changed) {
|
FunctionData* data = nullptr;
|
function_data_map.Lookup(script, mapping.first, &data);
|
FunctionData* new_data = nullptr;
|
function_data_map.Lookup(new_script, mapping.second, &new_data);
|
Handle<SharedFunctionInfo> sfi;
|
if (!data->shared.ToHandle(&sfi)) {
|
continue;
|
} else if (!data->should_restart) {
|
UNREACHABLE();
|
} else if (data->stack_position == FunctionData::ABOVE_BREAK_FRAME) {
|
status = debug::LiveEditResult::BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME;
|
} else if (data->stack_position ==
|
FunctionData::BELOW_NON_DROPPABLE_FRAME) {
|
status =
|
debug::LiveEditResult::BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME;
|
} else if (!data->running_generators.empty()) {
|
status = debug::LiveEditResult::BLOCKED_BY_RUNNING_GENERATOR;
|
} else if (data->stack_position == FunctionData::ARCHIVED_THREAD) {
|
status = debug::LiveEditResult::BLOCKED_BY_ACTIVE_FUNCTION;
|
}
|
if (status != debug::LiveEditResult::OK) {
|
result->status = status;
|
return false;
|
}
|
}
|
return true;
|
}
|
|
bool CanRestartFrame(Isolate* isolate, Address fp,
|
FunctionDataMap& function_data_map,
|
const LiteralMap& changed, debug::LiveEditResult* result) {
|
DCHECK_GT(fp, 0);
|
StackFrame* restart_frame = nullptr;
|
StackFrameIterator it(isolate);
|
for (; !it.done(); it.Advance()) {
|
if (it.frame()->fp() == fp) {
|
restart_frame = it.frame();
|
break;
|
}
|
}
|
DCHECK(restart_frame && restart_frame->is_java_script());
|
if (!LiveEdit::kFrameDropperSupported) {
|
result->status = debug::LiveEditResult::FRAME_RESTART_IS_NOT_SUPPORTED;
|
return false;
|
}
|
std::vector<Handle<SharedFunctionInfo>> sfis;
|
JavaScriptFrame::cast(restart_frame)->GetFunctions(&sfis);
|
for (auto& sfi : sfis) {
|
FunctionData* data = nullptr;
|
if (!function_data_map.Lookup(*sfi, &data)) continue;
|
auto new_literal_it = changed.find(data->literal);
|
if (new_literal_it == changed.end()) continue;
|
if (new_literal_it->second->scope()->new_target_var()) {
|
result->status =
|
debug::LiveEditResult::BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME;
|
return false;
|
}
|
}
|
return true;
|
}
|
|
void TranslateSourcePositionTable(Isolate* isolate, Handle<BytecodeArray> code,
|
const std::vector<SourceChangeRange>& diffs) {
|
SourcePositionTableBuilder builder;
|
|
Handle<ByteArray> source_position_table(code->SourcePositionTable(), isolate);
|
for (SourcePositionTableIterator iterator(*source_position_table);
|
!iterator.done(); iterator.Advance()) {
|
SourcePosition position = iterator.source_position();
|
position.SetScriptOffset(
|
LiveEdit::TranslatePosition(diffs, position.ScriptOffset()));
|
builder.AddPosition(iterator.code_offset(), position,
|
iterator.is_statement());
|
}
|
|
Handle<ByteArray> new_source_position_table(
|
builder.ToSourcePositionTable(isolate));
|
code->set_source_position_table(*new_source_position_table);
|
LOG_CODE_EVENT(isolate,
|
CodeLinePosInfoRecordEvent(code->GetFirstBytecodeAddress(),
|
*new_source_position_table));
|
}
|
|
void UpdatePositions(Isolate* isolate, Handle<SharedFunctionInfo> sfi,
|
const std::vector<SourceChangeRange>& diffs) {
|
int old_start_position = sfi->StartPosition();
|
int new_start_position =
|
LiveEdit::TranslatePosition(diffs, old_start_position);
|
int new_end_position = LiveEdit::TranslatePosition(diffs, sfi->EndPosition());
|
int new_function_token_position =
|
LiveEdit::TranslatePosition(diffs, sfi->function_token_position());
|
sfi->SetPosition(new_start_position, new_end_position);
|
sfi->SetFunctionTokenPosition(new_function_token_position,
|
new_start_position);
|
if (sfi->HasBytecodeArray()) {
|
TranslateSourcePositionTable(
|
isolate, handle(sfi->GetBytecodeArray(), isolate), diffs);
|
}
|
}
|
} // anonymous namespace
|
|
void LiveEdit::PatchScript(Isolate* isolate, Handle<Script> script,
|
Handle<String> new_source, bool preview,
|
debug::LiveEditResult* result) {
|
std::vector<SourceChangeRange> diffs;
|
LiveEdit::CompareStrings(isolate,
|
handle(String::cast(script->source()), isolate),
|
new_source, &diffs);
|
if (diffs.empty()) {
|
result->status = debug::LiveEditResult::OK;
|
return;
|
}
|
|
ParseInfo parse_info(isolate, script);
|
std::vector<FunctionLiteral*> literals;
|
if (!ParseScript(isolate, &parse_info, false, &literals, result)) return;
|
|
Handle<Script> new_script = isolate->factory()->CloneScript(script);
|
new_script->set_source(*new_source);
|
std::vector<FunctionLiteral*> new_literals;
|
ParseInfo new_parse_info(isolate, new_script);
|
if (!ParseScript(isolate, &new_parse_info, true, &new_literals, result)) {
|
return;
|
}
|
|
FunctionLiteralChanges literal_changes;
|
CalculateFunctionLiteralChanges(literals, diffs, &literal_changes);
|
|
LiteralMap changed;
|
LiteralMap unchanged;
|
MapLiterals(literal_changes, new_literals, &unchanged, &changed);
|
|
FunctionDataMap function_data_map;
|
for (const auto& mapping : changed) {
|
function_data_map.AddInterestingLiteral(script->id(), mapping.first, true);
|
function_data_map.AddInterestingLiteral(new_script->id(), mapping.second,
|
false);
|
}
|
for (const auto& mapping : unchanged) {
|
function_data_map.AddInterestingLiteral(script->id(), mapping.first, false);
|
}
|
Address restart_frame_fp = 0;
|
function_data_map.Fill(isolate, &restart_frame_fp);
|
|
if (!CanPatchScript(changed, script, new_script, function_data_map, result)) {
|
return;
|
}
|
if (restart_frame_fp &&
|
!CanRestartFrame(isolate, restart_frame_fp, function_data_map, changed,
|
result)) {
|
return;
|
}
|
|
if (preview) {
|
result->status = debug::LiveEditResult::OK;
|
return;
|
}
|
|
std::map<int, int> start_position_to_unchanged_id;
|
for (const auto& mapping : unchanged) {
|
FunctionData* data = nullptr;
|
if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
|
Handle<SharedFunctionInfo> sfi;
|
if (!data->shared.ToHandle(&sfi)) continue;
|
DCHECK_EQ(sfi->script(), *script);
|
|
isolate->compilation_cache()->Remove(sfi);
|
isolate->debug()->DeoptimizeFunction(sfi);
|
if (sfi->HasDebugInfo()) {
|
Handle<DebugInfo> debug_info(sfi->GetDebugInfo(), isolate);
|
isolate->debug()->RemoveBreakInfoAndMaybeFree(debug_info);
|
}
|
UpdatePositions(isolate, sfi, diffs);
|
|
sfi->set_script(*new_script);
|
if (sfi->HasUncompiledData()) {
|
sfi->uncompiled_data()->set_function_literal_id(
|
mapping.second->function_literal_id());
|
}
|
new_script->shared_function_infos()->Set(
|
mapping.second->function_literal_id(), HeapObjectReference::Weak(*sfi));
|
DCHECK_EQ(sfi->FunctionLiteralId(isolate),
|
mapping.second->function_literal_id());
|
|
// Save the new start_position -> id mapping, so that we can recover it when
|
// iterating over changed functions' constant pools.
|
start_position_to_unchanged_id[mapping.second->start_position()] =
|
mapping.second->function_literal_id();
|
|
if (sfi->HasUncompiledDataWithPreParsedScope()) {
|
sfi->ClearPreParsedScopeData();
|
}
|
|
for (auto& js_function : data->js_functions) {
|
js_function->set_feedback_cell(*isolate->factory()->many_closures_cell());
|
if (!js_function->is_compiled()) continue;
|
JSFunction::EnsureFeedbackVector(js_function);
|
}
|
|
if (!sfi->HasBytecodeArray()) continue;
|
FixedArray* constants = sfi->GetBytecodeArray()->constant_pool();
|
for (int i = 0; i < constants->length(); ++i) {
|
if (!constants->get(i)->IsSharedFunctionInfo()) continue;
|
FunctionData* data = nullptr;
|
if (!function_data_map.Lookup(SharedFunctionInfo::cast(constants->get(i)),
|
&data)) {
|
continue;
|
}
|
auto change_it = changed.find(data->literal);
|
if (change_it == changed.end()) continue;
|
if (!function_data_map.Lookup(new_script, change_it->second, &data)) {
|
continue;
|
}
|
Handle<SharedFunctionInfo> new_sfi;
|
if (!data->shared.ToHandle(&new_sfi)) continue;
|
constants->set(i, *new_sfi);
|
}
|
}
|
for (const auto& mapping : changed) {
|
FunctionData* data = nullptr;
|
if (!function_data_map.Lookup(new_script, mapping.second, &data)) continue;
|
Handle<SharedFunctionInfo> new_sfi = data->shared.ToHandleChecked();
|
DCHECK_EQ(new_sfi->script(), *new_script);
|
|
if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
|
Handle<SharedFunctionInfo> sfi;
|
if (!data->shared.ToHandle(&sfi)) continue;
|
|
isolate->debug()->DeoptimizeFunction(sfi);
|
isolate->compilation_cache()->Remove(sfi);
|
for (auto& js_function : data->js_functions) {
|
js_function->set_shared(*new_sfi);
|
js_function->set_code(js_function->shared()->GetCode());
|
|
js_function->set_feedback_cell(*isolate->factory()->many_closures_cell());
|
if (!js_function->is_compiled()) continue;
|
JSFunction::EnsureFeedbackVector(js_function);
|
}
|
|
if (!new_sfi->HasBytecodeArray()) continue;
|
FixedArray* constants = new_sfi->GetBytecodeArray()->constant_pool();
|
for (int i = 0; i < constants->length(); ++i) {
|
if (!constants->get(i)->IsSharedFunctionInfo()) continue;
|
SharedFunctionInfo* inner_sfi =
|
SharedFunctionInfo::cast(constants->get(i));
|
|
// See if there is a mapping from this function's start position to a
|
// unchanged function's id.
|
auto unchanged_it =
|
start_position_to_unchanged_id.find(inner_sfi->StartPosition());
|
if (unchanged_it == start_position_to_unchanged_id.end()) continue;
|
|
// Grab that function id from the new script's SFI list, which should have
|
// already been updated in in the unchanged pass.
|
SharedFunctionInfo* old_unchanged_inner_sfi =
|
SharedFunctionInfo::cast(new_script->shared_function_infos()
|
->Get(unchanged_it->second)
|
->GetHeapObject());
|
// Now some sanity checks. Make sure that this inner_sfi is not the
|
// unchanged SFI yet...
|
DCHECK_NE(old_unchanged_inner_sfi, inner_sfi);
|
// ... and that the unchanged SFI has already been processed and patched
|
// to be on the new script ...
|
DCHECK_EQ(old_unchanged_inner_sfi->script(), *new_script);
|
|
constants->set(i, old_unchanged_inner_sfi);
|
}
|
}
|
#ifdef DEBUG
|
{
|
// Check that all the functions in the new script are valid, that their
|
// function literals match what is expected, and that start positions are
|
// unique.
|
DisallowHeapAllocation no_gc;
|
|
SharedFunctionInfo::ScriptIterator it(isolate, *new_script);
|
std::set<int> start_positions;
|
while (SharedFunctionInfo* sfi = it.Next()) {
|
DCHECK_EQ(sfi->script(), *new_script);
|
DCHECK_EQ(sfi->FunctionLiteralId(isolate), it.CurrentIndex());
|
// Don't check the start position of the top-level function, as it can
|
// overlap with a function in the script.
|
if (sfi->is_toplevel()) {
|
DCHECK_EQ(start_positions.find(sfi->StartPosition()),
|
start_positions.end());
|
start_positions.insert(sfi->StartPosition());
|
}
|
|
if (!sfi->HasBytecodeArray()) continue;
|
// Check that all the functions in this function's constant pool are also
|
// on the new script, and that their id matches their index in the new
|
// scripts function list.
|
FixedArray* constants = sfi->GetBytecodeArray()->constant_pool();
|
for (int i = 0; i < constants->length(); ++i) {
|
if (!constants->get(i)->IsSharedFunctionInfo()) continue;
|
SharedFunctionInfo* inner_sfi =
|
SharedFunctionInfo::cast(constants->get(i));
|
DCHECK_EQ(inner_sfi->script(), *new_script);
|
DCHECK_EQ(inner_sfi, new_script->shared_function_infos()
|
->Get(inner_sfi->FunctionLiteralId(isolate))
|
->GetHeapObject());
|
}
|
}
|
}
|
#endif
|
|
if (restart_frame_fp) {
|
for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
|
if (it.frame()->fp() == restart_frame_fp) {
|
isolate->debug()->ScheduleFrameRestart(it.frame());
|
result->stack_changed = true;
|
break;
|
}
|
}
|
}
|
|
int script_id = script->id();
|
script->set_id(new_script->id());
|
new_script->set_id(script_id);
|
result->status = debug::LiveEditResult::OK;
|
result->script = ToApiHandle<v8::debug::Script>(new_script);
|
}
|
|
void LiveEdit::InitializeThreadLocal(Debug* debug) {
|
debug->thread_local_.restart_fp_ = 0;
|
}
|
|
bool LiveEdit::RestartFrame(JavaScriptFrame* frame) {
|
if (!LiveEdit::kFrameDropperSupported) return false;
|
Isolate* isolate = frame->isolate();
|
StackFrame::Id break_frame_id = isolate->debug()->break_frame_id();
|
bool break_frame_found = break_frame_id == StackFrame::NO_ID;
|
for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
|
StackFrame* current = it.frame();
|
break_frame_found = break_frame_found || break_frame_id == current->id();
|
if (current->fp() == frame->fp()) {
|
if (break_frame_found) {
|
isolate->debug()->ScheduleFrameRestart(current);
|
return true;
|
} else {
|
return false;
|
}
|
}
|
if (!break_frame_found) continue;
|
if (current->is_exit() || current->is_builtin_exit()) {
|
return false;
|
}
|
if (!current->is_java_script()) continue;
|
std::vector<Handle<SharedFunctionInfo>> shareds;
|
JavaScriptFrame::cast(current)->GetFunctions(&shareds);
|
for (auto& shared : shareds) {
|
if (IsResumableFunction(shared->kind())) {
|
return false;
|
}
|
}
|
}
|
return false;
|
}
|
|
void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1,
|
Handle<String> s2,
|
std::vector<SourceChangeRange>* diffs) {
|
s1 = String::Flatten(isolate, s1);
|
s2 = String::Flatten(isolate, s2);
|
|
LineEndsWrapper line_ends1(isolate, s1);
|
LineEndsWrapper line_ends2(isolate, s2);
|
|
LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
|
TokenizingLineArrayCompareOutput output(isolate, line_ends1, line_ends2, s1,
|
s2, diffs);
|
|
NarrowDownInput(&input, &output);
|
|
Comparator::CalculateDifference(&input, &output);
|
}
|
|
int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& diffs,
|
int position) {
|
auto it = std::lower_bound(diffs.begin(), diffs.end(), position,
|
[](const SourceChangeRange& change, int position) {
|
return change.end_position < position;
|
});
|
if (it != diffs.end() && position == it->end_position) {
|
return it->new_end_position;
|
}
|
if (it == diffs.begin()) return position;
|
DCHECK(it == diffs.end() || position <= it->start_position);
|
it = std::prev(it);
|
return position + (it->new_end_position - it->end_position);
|
}
|
} // namespace internal
|
} // namespace v8
|