Commit 5505c664 authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

Reland "[debug] liveedit in native"

This is a reland of 3dfaf826

Original change's description:
> [debug] liveedit in native
>
> Liveedit step-by-step:
> 1. calculate diff between old source and new source,
> 2. map function literals from old source to new source,
> 3. create new script for new_source,
> 4. mark literals with changed code as changed, all others as unchanged,
> 5. check that for changed literals there are no:
>   - running generators in the heap,
>   - non droppable frames (e.g. running generator) above them on stack.
> 6. mark the bottom most frame with changed function as scheduled for
>    restart if any.
> 7. for unchanged functions:
>   - deoptimize,
>   - remove from cache,
>   - update source positions,
>   - move to new script,
>   - reset feedback information and preparsed scope information if any,
>   - replace any sfi in constant pool with changed one if any.
> 8. for changed functions:
>   - deoptimize
>   - remove from cache,
>   - reset feedback information,
>   - update all links from js functions to old shared with new one.
> 9. swap scripts.
>
> TBR=ulan@chromium.org
>
> Bug: v8:7862,v8:5713
> Cq-Include-Trybots: luci.chromium.try:linux_chromium_headless_rel;luci.chromium.try:linux_chromium_rel_ng;master.tryserver.blink:linux_trusty_blink_rel
> Change-Id: I8f6f6156318cc82d6f36d7ebc1c9f7d5f3aa1461
> Reviewed-on: https://chromium-review.googlesource.com/1105493
> Reviewed-by: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
> Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
> Reviewed-by: Yang Guo <yangguo@chromium.org>
> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#54146}

TBR=dgozman@chromium.org

Bug: v8:7862, v8:5713
Change-Id: I163ed2fd2ca3115ba0de74cb35a6fac9e40fdd94
Cq-Include-Trybots: luci.chromium.try:linux_chromium_headless_rel;luci.chromium.try:linux_chromium_rel_ng;master.tryserver.blink:linux_trusty_blink_rel
Reviewed-on: https://chromium-review.googlesource.com/1124879
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: 's avatarAleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54187}
parent 6406b661
...@@ -661,7 +661,6 @@ action("js2c") { ...@@ -661,7 +661,6 @@ action("js2c") {
"src/js/prologue.js", "src/js/prologue.js",
"src/js/array.js", "src/js/array.js",
"src/js/typedarray.js", "src/js/typedarray.js",
"src/debug/liveedit.js",
] ]
outputs = [ outputs = [
...@@ -2295,7 +2294,6 @@ v8_source_set("v8_base") { ...@@ -2295,7 +2294,6 @@ v8_source_set("v8_base") {
"src/runtime/runtime-interpreter.cc", "src/runtime/runtime-interpreter.cc",
"src/runtime/runtime-intl.cc", "src/runtime/runtime-intl.cc",
"src/runtime/runtime-literals.cc", "src/runtime/runtime-literals.cc",
"src/runtime/runtime-liveedit.cc",
"src/runtime/runtime-maths.cc", "src/runtime/runtime-maths.cc",
"src/runtime/runtime-module.cc", "src/runtime/runtime-module.cc",
"src/runtime/runtime-numbers.cc", "src/runtime/runtime-numbers.cc",
......
...@@ -1200,37 +1200,9 @@ bool Compiler::CompileOptimized(Handle<JSFunction> function, ...@@ -1200,37 +1200,9 @@ bool Compiler::CompileOptimized(Handle<JSFunction> function,
return true; return true;
} }
MaybeHandle<JSArray> Compiler::CompileForLiveEdit(Handle<Script> script) { MaybeHandle<SharedFunctionInfo> Compiler::CompileForLiveEdit(
Isolate* isolate = script->GetIsolate(); ParseInfo* parse_info, Isolate* isolate) {
DCHECK(AllowCompilation::IsAllowed(isolate)); return CompileToplevel(parse_info, isolate);
// In order to ensure that live edit function info collection finds the newly
// generated shared function infos, clear the script's list temporarily
// and restore it at the end of this method.
Handle<WeakFixedArray> old_function_infos(script->shared_function_infos(),
isolate);
script->set_shared_function_infos(isolate->heap()->empty_weak_fixed_array());
// Start a compilation.
ParseInfo parse_info(isolate, script);
parse_info.set_eager();
// TODO(635): support extensions.
Handle<JSArray> infos;
Handle<SharedFunctionInfo> shared_info;
if (CompileToplevel(&parse_info, isolate).ToHandle(&shared_info)) {
// Check postconditions on success.
DCHECK(!isolate->has_pending_exception());
infos = LiveEditFunctionTracker::Collect(parse_info.literal(), script,
parse_info.zone(), isolate);
}
// Restore the original function info list in order to remain side-effect
// free as much as possible, since some code expects the old shared function
// infos to stick around.
script->set_shared_function_infos(*old_function_infos);
return infos;
} }
MaybeHandle<JSFunction> Compiler::GetFunctionFromEval( MaybeHandle<JSFunction> Compiler::GetFunctionFromEval(
......
...@@ -57,7 +57,9 @@ class V8_EXPORT_PRIVATE Compiler : public AllStatic { ...@@ -57,7 +57,9 @@ class V8_EXPORT_PRIVATE Compiler : public AllStatic {
ClearExceptionFlag flag); ClearExceptionFlag flag);
static bool Compile(Handle<JSFunction> function, ClearExceptionFlag flag); static bool Compile(Handle<JSFunction> function, ClearExceptionFlag flag);
static bool CompileOptimized(Handle<JSFunction> function, ConcurrencyMode); static bool CompileOptimized(Handle<JSFunction> function, ConcurrencyMode);
static MaybeHandle<JSArray> CompileForLiveEdit(Handle<Script> script);
V8_WARN_UNUSED_RESULT static MaybeHandle<SharedFunctionInfo>
CompileForLiveEdit(ParseInfo* parse_info, Isolate* isolate);
// Creates a new task that when run will parse and compile the streamed // Creates a new task that when run will parse and compile the streamed
// script associated with |streaming_data| and can be finalized with // script associated with |streaming_data| and can be finalized with
......
...@@ -79,6 +79,8 @@ void BreakRightNow(Isolate* isolate); ...@@ -79,6 +79,8 @@ void BreakRightNow(Isolate* isolate);
bool AllFramesOnStackAreBlackboxed(Isolate* isolate); bool AllFramesOnStackAreBlackboxed(Isolate* isolate);
class Script;
struct LiveEditResult { struct LiveEditResult {
enum Status { enum Status {
OK, OK,
...@@ -92,6 +94,8 @@ struct LiveEditResult { ...@@ -92,6 +94,8 @@ struct LiveEditResult {
}; };
Status status = OK; Status status = OK;
bool stack_changed = false; bool stack_changed = false;
// Available only for OK.
v8::Local<v8::debug::Script> script;
// Fields below are available only for COMPILE_ERROR. // Fields below are available only for COMPILE_ERROR.
v8::Local<v8::String> message; v8::Local<v8::String> message;
int line_number = -1; int line_number = -1;
......
...@@ -1910,62 +1910,12 @@ bool Debug::CanBreakAtEntry(Handle<SharedFunctionInfo> shared) { ...@@ -1910,62 +1910,12 @@ bool Debug::CanBreakAtEntry(Handle<SharedFunctionInfo> shared) {
} }
bool Debug::SetScriptSource(Handle<Script> script, Handle<String> source, bool Debug::SetScriptSource(Handle<Script> script, Handle<String> source,
bool preview, debug::LiveEditResult* output) { bool preview, debug::LiveEditResult* result) {
SaveContext save(isolate_);
StackFrame::Id frame_id = break_frame_id();
DebugScope debug_scope(this); DebugScope debug_scope(this);
if (debug_scope.failed()) return false;
isolate_->set_context(*debug_context());
if (frame_id != StackFrame::NO_ID) {
thread_local_.break_frame_id_ = frame_id;
}
Handle<Object> script_wrapper = Script::GetWrapper(script);
Handle<Object> argv[] = {script_wrapper, source,
isolate_->factory()->ToBoolean(preview),
isolate_->factory()->NewJSArray(0)};
Handle<Object> result;
MaybeHandle<Object> maybe_exception;
running_live_edit_ = true; running_live_edit_ = true;
if (!CallFunction("SetScriptSource", arraysize(argv), argv, &maybe_exception) LiveEdit::PatchScript(isolate_, script, source, preview, result);
.ToHandle(&result)) {
Handle<Object> pending_exception = maybe_exception.ToHandleChecked();
if (pending_exception->IsJSObject()) {
Handle<JSObject> exception = Handle<JSObject>::cast(pending_exception);
Handle<String> message = Handle<String>::cast(
JSReceiver::GetProperty(isolate_, exception, "message")
.ToHandleChecked());
Handle<String> blocked_message =
isolate_->factory()->NewStringFromAsciiChecked(
"Blocked by functions on stack");
if (blocked_message->Equals(*message)) {
output->status = debug::LiveEditResult::
BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME;
} else {
Handle<JSObject> details = Handle<JSObject>::cast(
JSReceiver::GetProperty(isolate_, exception, "details")
.ToHandleChecked());
Handle<String> error = Handle<String>::cast(
JSReceiver::GetProperty(isolate_, details, "syntaxErrorMessage")
.ToHandleChecked());
output->status = debug::LiveEditResult::COMPILE_ERROR;
output->line_number = kNoSourcePosition;
output->column_number = kNoSourcePosition;
output->message = Utils::ToLocal(error);
}
}
running_live_edit_ = false;
return false;
}
Handle<Object> stack_changed_value =
JSReceiver::GetProperty(isolate_, Handle<JSObject>::cast(result),
"stack_modified")
.ToHandleChecked();
output->stack_changed = stack_changed_value->IsTrue(isolate_);
output->status = debug::LiveEditResult::OK;
running_live_edit_ = false; running_live_edit_ = false;
return true; return result->status == debug::LiveEditResult::OK;
} }
void Debug::OnCompileError(Handle<Script> script) { void Debug::OnCompileError(Handle<Script> script) {
...@@ -1977,6 +1927,9 @@ void Debug::OnAfterCompile(Handle<Script> script) { ...@@ -1977,6 +1927,9 @@ void Debug::OnAfterCompile(Handle<Script> script) {
} }
void Debug::ProcessCompileEvent(bool has_compile_error, Handle<Script> script) { void Debug::ProcessCompileEvent(bool has_compile_error, Handle<Script> script) {
// TODO(kozyatinskiy): teach devtools to work with liveedit scripts better
// first and then remove this fast return.
if (running_live_edit_) return;
// Attach the correct debug id to the script. The debug id is used by the // Attach the correct debug id to the script. The debug id is used by the
// inspector to filter scripts by native context. // inspector to filter scripts by native context.
script->set_context_data(isolate_->native_context()->debug_context_id()); script->set_context_data(isolate_->native_context()->debug_context_id());
...@@ -1996,7 +1949,6 @@ void Debug::ProcessCompileEvent(bool has_compile_error, Handle<Script> script) { ...@@ -1996,7 +1949,6 @@ void Debug::ProcessCompileEvent(bool has_compile_error, Handle<Script> script) {
running_live_edit_, has_compile_error); running_live_edit_, has_compile_error);
} }
Handle<Context> Debug::GetDebugContext() { Handle<Context> Debug::GetDebugContext() {
if (!is_loaded()) return Handle<Context>(); if (!is_loaded()) return Handle<Context>();
DebugScope debug_scope(this); DebugScope debug_scope(this);
......
...@@ -396,6 +396,8 @@ class Debug { ...@@ -396,6 +396,8 @@ class Debug {
// source position for break points. // source position for break points.
static const int kBreakAtEntryPosition = 0; static const int kBreakAtEntryPosition = 0;
void RemoveBreakInfoAndMaybeFree(Handle<DebugInfo> debug_info);
private: private:
explicit Debug(Isolate* isolate); explicit Debug(Isolate* isolate);
~Debug(); ~Debug();
...@@ -474,7 +476,6 @@ class Debug { ...@@ -474,7 +476,6 @@ class Debug {
typedef std::function<void(Handle<DebugInfo>)> DebugInfoClearFunction; typedef std::function<void(Handle<DebugInfo>)> DebugInfoClearFunction;
void ClearAllDebugInfos(DebugInfoClearFunction clear_function); void ClearAllDebugInfos(DebugInfoClearFunction clear_function);
void RemoveBreakInfoAndMaybeFree(Handle<DebugInfo> debug_info);
void FindDebugInfo(Handle<DebugInfo> debug_info, DebugInfoListNode** prev, void FindDebugInfo(Handle<DebugInfo> debug_info, DebugInfoListNode** prev,
DebugInfoListNode** curr); DebugInfoListNode** curr);
void FreeDebugInfoListNode(DebugInfoListNode* prev, DebugInfoListNode* node); void FreeDebugInfoListNode(DebugInfoListNode* prev, DebugInfoListNode* node);
......
...@@ -4,21 +4,23 @@ ...@@ -4,21 +4,23 @@
#include "src/debug/liveedit.h" #include "src/debug/liveedit.h"
#include "src/assembler-inl.h" #include "src/api.h"
#include "src/ast/ast-traversal-visitor.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h" #include "src/ast/scopes.h"
#include "src/code-stubs.h"
#include "src/compilation-cache.h" #include "src/compilation-cache.h"
#include "src/compiler.h" #include "src/compiler.h"
#include "src/debug/debug-interface.h"
#include "src/debug/debug.h" #include "src/debug/debug.h"
#include "src/deoptimizer.h"
#include "src/frames-inl.h" #include "src/frames-inl.h"
#include "src/global-handles.h"
#include "src/isolate-inl.h" #include "src/isolate-inl.h"
#include "src/messages.h" #include "src/messages.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
#include "src/objects/hash-table-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/source-position-table.h" #include "src/source-position-table.h"
#include "src/v8.h" #include "src/v8.h"
#include "src/v8memory.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -53,17 +55,6 @@ class Comparator { ...@@ -53,17 +55,6 @@ class Comparator {
static void CalculateDifference(Input* input, Output* result_writer); static void CalculateDifference(Input* input, Output* result_writer);
}; };
void SetElementSloppy(Handle<JSObject> object,
uint32_t index,
Handle<Object> value) {
// Ignore return value from SetElement. It can only be a failure if there
// are element setters causing exceptions and the debugger context has none
// of these.
Object::SetElement(object->GetIsolate(), object, index, value,
LanguageMode::kSloppy)
.Assert();
}
// A simple implementation of dynamic programming algorithm. It solves // A simple implementation of dynamic programming algorithm. It solves
// the problem of finding the difference of 2 arrays. It uses a table of results // 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 // of subproblems. Each cell contains a number together with 2-bit flag
...@@ -336,40 +327,6 @@ void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) { ...@@ -336,40 +327,6 @@ void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) {
} }
} }
// A helper class that writes chunk numbers into JSArray.
// Each chunk is stored as 3 array elements: (pos1_begin, pos1_end, pos2_end).
class CompareOutputArrayWriter {
public:
explicit CompareOutputArrayWriter(Isolate* isolate)
: array_(isolate->factory()->NewJSArray(10)), current_size_(0) {}
Handle<JSArray> GetResult() {
return array_;
}
void WriteChunk(int char_pos1, int char_pos2, int char_len1, int char_len2) {
Isolate* isolate = array_->GetIsolate();
SetElementSloppy(array_,
current_size_,
Handle<Object>(Smi::FromInt(char_pos1), isolate));
SetElementSloppy(array_,
current_size_ + 1,
Handle<Object>(Smi::FromInt(char_pos1 + char_len1),
isolate));
SetElementSloppy(array_,
current_size_ + 2,
Handle<Object>(Smi::FromInt(char_pos2 + char_len2),
isolate));
current_size_ += 3;
}
private:
Handle<JSArray> array_;
int current_size_;
};
// Represents 2 strings as 2 arrays of tokens. // Represents 2 strings as 2 arrays of tokens.
// TODO(LiveEdit): Currently it's actually an array of charactres. // TODO(LiveEdit): Currently it's actually an array of charactres.
// Make array of tokens instead. // Make array of tokens instead.
...@@ -557,394 +514,438 @@ class TokenizingLineArrayCompareOutput : public SubrangableOutput { ...@@ -557,394 +514,438 @@ class TokenizingLineArrayCompareOutput : public SubrangableOutput {
int subrange_offset2_; int subrange_offset2_;
std::vector<SourceChangeRange>* output_; std::vector<SourceChangeRange>* output_;
}; };
} // anonymous namespace
Handle<JSArray> LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1,
Handle<String> s2) {
std::vector<SourceChangeRange> changes;
CompareStrings(isolate, s1, s2, &changes);
CompareOutputArrayWriter writer(isolate);
for (const auto& change : changes) {
writer.WriteChunk(change.start_position, change.new_start_position,
change.end_position - change.start_position,
change.new_end_position - change.new_start_position);
}
return writer.GetResult();
}
// Unwraps JSValue object, returning its field "value"
static Handle<Object> UnwrapJSValue(Handle<JSValue> jsValue) {
return Handle<Object>(jsValue->value(), jsValue->GetIsolate());
}
// Wraps any object into a OpaqueReference, that will hide the object
// from JavaScript.
static Handle<JSValue> WrapInJSValue(Isolate* isolate,
Handle<HeapObject> object) {
Handle<JSFunction> constructor = isolate->opaque_reference_function();
Handle<JSValue> result =
Handle<JSValue>::cast(isolate->factory()->NewJSObject(constructor));
result->set_value(*object);
return result;
}
static Handle<SharedFunctionInfo> UnwrapSharedFunctionInfoFromJSValue(
Handle<JSValue> jsValue) {
Object* shared = jsValue->value();
CHECK(shared->IsSharedFunctionInfo());
return Handle<SharedFunctionInfo>(SharedFunctionInfo::cast(shared),
jsValue->GetIsolate());
}
struct SourcePositionEvent {
enum Type { LITERAL_STARTS, LITERAL_ENDS, DIFF_STARTS, DIFF_ENDS };
static int GetArrayLength(Handle<JSArray> array) { int position;
Object* length = array->length(); Type type;
CHECK(length->IsSmi());
return Smi::ToInt(length);
}
void FunctionInfoWrapper::SetInitialProperties(Handle<String> name,
int start_position,
int end_position, int param_num,
int parent_index,
int function_literal_id) {
HandleScope scope(isolate());
this->SetField(kFunctionNameOffset_, name);
this->SetSmiValueField(kStartPositionOffset_, start_position);
this->SetSmiValueField(kEndPositionOffset_, end_position);
this->SetSmiValueField(kParamNumOffset_, param_num);
this->SetSmiValueField(kParentIndexOffset_, parent_index);
this->SetSmiValueField(kFunctionLiteralIdOffset_, function_literal_id);
}
void FunctionInfoWrapper::SetSharedFunctionInfo(
Isolate* isolate, Handle<SharedFunctionInfo> info) {
Handle<JSValue> info_holder = WrapInJSValue(isolate, info);
this->SetField(kSharedFunctionInfoOffset_, info_holder);
}
Handle<SharedFunctionInfo> FunctionInfoWrapper::GetSharedFunctionInfo() {
Handle<Object> element = this->GetField(kSharedFunctionInfoOffset_);
Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element);
Handle<Object> raw_result = UnwrapJSValue(value_wrapper);
CHECK(raw_result->IsSharedFunctionInfo());
return Handle<SharedFunctionInfo>::cast(raw_result);
}
void SharedInfoWrapper::SetProperties(Handle<String> name, union {
int start_position, FunctionLiteral* literal;
int end_position, int pos_diff;
Handle<SharedFunctionInfo> info) { };
HandleScope scope(isolate());
this->SetField(kFunctionNameOffset_, name);
Handle<JSValue> info_holder = WrapInJSValue(scope.isolate(), info);
this->SetField(kSharedInfoOffset_, info_holder);
this->SetSmiValueField(kStartPositionOffset_, start_position);
this->SetSmiValueField(kEndPositionOffset_, end_position);
}
Handle<SharedFunctionInfo> SharedInfoWrapper::GetInfo() {
Handle<Object> element = this->GetField(kSharedInfoOffset_);
Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element);
return UnwrapSharedFunctionInfoFromJSValue(value_wrapper);
}
void LiveEdit::InitializeThreadLocal(Debug* debug) {
debug->thread_local_.restart_fp_ = 0;
}
MaybeHandle<JSArray> LiveEdit::GatherCompileInfo(Isolate* isolate,
Handle<Script> script,
Handle<String> source) {
MaybeHandle<JSArray> infos;
Handle<Object> original_source =
Handle<Object>(script->source(), isolate);
script->set_source(*source);
{ SourcePositionEvent(FunctionLiteral* literal, bool is_start)
// Creating verbose TryCatch from public API is currently the only way to : position(is_start ? literal->start_position()
// force code save location. We do not use this the object directly. : literal->end_position()),
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate)); type(is_start ? LITERAL_STARTS : LITERAL_ENDS),
try_catch.SetVerbose(true); literal(literal) {}
SourcePositionEvent(const SourceChangeRange& change, bool is_start)
// A logical 'try' section. : position(is_start ? change.start_position : change.end_position),
infos = Compiler::CompileForLiveEdit(script); type(is_start ? DIFF_STARTS : DIFF_ENDS),
} pos_diff((change.new_end_position - change.new_start_position) -
(change.end_position - change.start_position)) {}
// A logical 'catch' section.
Handle<JSObject> rethrow_exception; static bool LessThan(const SourcePositionEvent& a,
if (isolate->has_pending_exception()) { const SourcePositionEvent& b) {
Handle<Object> exception(isolate->pending_exception(), isolate); if (a.position != b.position) return a.position < b.position;
MessageLocation message_location = isolate->GetMessageLocation(); if (a.type != b.type) return a.type < b.type;
if (a.type == LITERAL_STARTS && b.type == LITERAL_STARTS) {
isolate->clear_pending_message(); return a.literal->end_position() < b.literal->end_position();
isolate->clear_pending_exception(); } else if (a.type == LITERAL_ENDS && b.type == LITERAL_ENDS) {
return a.literal->start_position() > b.literal->start_position();
// If possible, copy positions from message object to exception object. } else {
if (exception->IsJSObject() && !message_location.script().is_null()) { return a.pos_diff < b.pos_diff;
rethrow_exception = Handle<JSObject>::cast(exception);
Factory* factory = isolate->factory();
Handle<String> start_pos_key = factory->InternalizeOneByteString(
STATIC_CHAR_VECTOR("startPosition"));
Handle<String> end_pos_key =
factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("endPosition"));
Handle<String> script_obj_key =
factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("scriptObject"));
Handle<Smi> start_pos(
Smi::FromInt(message_location.start_pos()), isolate);
Handle<Smi> end_pos(Smi::FromInt(message_location.end_pos()), isolate);
Handle<JSObject> script_obj =
Script::GetWrapper(message_location.script());
Object::SetProperty(rethrow_exception, start_pos_key, start_pos,
LanguageMode::kSloppy)
.Assert();
Object::SetProperty(rethrow_exception, end_pos_key, end_pos,
LanguageMode::kSloppy)
.Assert();
Object::SetProperty(rethrow_exception, script_obj_key, script_obj,
LanguageMode::kSloppy)
.Assert();
} }
} }
};
// A logical 'finally' section. struct FunctionLiteralChange {
script->set_source(*original_source); // 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) {}
};
if (rethrow_exception.is_null()) { using FunctionLiteralChanges =
return infos.ToHandleChecked(); std::unordered_map<FunctionLiteral*, FunctionLiteralChange>;
} else { void CalculateFunctionLiteralChanges(
return isolate->Throw<JSArray>(rethrow_exception); 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;
}
} }
} }
// Patch function feedback vector. // Function which has not changed itself, but if any variable in its
// The feedback vector is a cache for complex object boilerplates and for a // outer context has been added/removed, we must consider this function
// native context. We must clean cached values, or if the structure of the // as damaged and not update references to it.
// vector itself changes we need to allocate a new one. // This is because old compiled function has hardcoded references to
class FeedbackVectorFixer { // it's outer context.
public: bool HasChangedScope(FunctionLiteral* a, FunctionLiteral* b) {
static void PatchFeedbackVector(FunctionInfoWrapper* compile_info_wrapper, Scope* scope_a = a->scope()->outer_scope();
Handle<SharedFunctionInfo> shared_info, Scope* scope_b = b->scope()->outer_scope();
Isolate* isolate) { while (scope_a && scope_b) {
// When feedback metadata changes, we have to create new array instances. std::unordered_map<int, Handle<String>> vars;
// Since we cannot create instances when iterating heap, we should first for (Variable* var : *scope_a->locals()) {
// collect all functions and fix their literal arrays. if (!var->IsContextSlot()) continue;
Handle<FixedArray> function_instances = vars[var->index()] = var->name();
CollectJSFunctions(shared_info, isolate); }
for (Variable* var : *scope_b->locals()) {
for (int i = 0; i < function_instances->length(); i++) { if (!var->IsContextSlot()) continue;
Handle<JSFunction> fun(JSFunction::cast(function_instances->get(i)), auto it = vars.find(var->index());
isolate); if (it == vars.end()) return true;
Handle<FeedbackCell> feedback_cell = if (*it->second != *var->name()) return true;
isolate->factory()->NewManyClosuresCell(
isolate->factory()->undefined_value());
fun->set_feedback_cell(*feedback_cell);
// Only create feedback vectors if we already have the metadata.
if (shared_info->is_compiled()) JSFunction::EnsureFeedbackVector(fun);
} }
scope_a = scope_a->outer_scope();
scope_b = scope_b->outer_scope();
} }
return scope_a != scope_b;
}
private: enum ChangeState { UNCHANGED, CHANGED, DAMAGED };
// Iterates all function instances in the HEAP that refers to the
// provided shared_info. using LiteralMap = std::unordered_map<FunctionLiteral*, FunctionLiteral*>;
template<typename Visitor> void MapLiterals(const FunctionLiteralChanges& changes,
static void IterateJSFunctions(Handle<SharedFunctionInfo> shared_info, const std::vector<FunctionLiteral*>& new_literals,
Visitor* visitor) { LiteralMap* unchanged, LiteralMap* changed) {
HeapIterator iterator(shared_info->GetHeap()); std::map<std::pair<int, int>, FunctionLiteral*> position_to_new_literal;
for (HeapObject* obj = iterator.next(); obj != nullptr; for (FunctionLiteral* literal : new_literals) {
obj = iterator.next()) { DCHECK(literal->start_position() != kNoSourcePosition);
if (obj->IsJSFunction()) { DCHECK(literal->end_position() != kNoSourcePosition);
JSFunction* function = JSFunction::cast(obj); position_to_new_literal[std::make_pair(literal->start_position(),
if (function->shared() == *shared_info) { literal->end_position())] = literal;
visitor->visit(function); }
} 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;
auto it = position_to_new_literal.find(
std::make_pair(change.new_start_position, change.new_end_position));
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) {
// Finds all instances of JSFunction that refers to the provided shared_info if (change_state[mapping.first] == ChangeState::UNCHANGED) {
// and returns array with them. (*unchanged)[mapping.first] = mapping.second;
static Handle<FixedArray> CollectJSFunctions( } else if (change_state[mapping.first] == ChangeState::CHANGED) {
Handle<SharedFunctionInfo> shared_info, Isolate* isolate) { (*changed)[mapping.first] = mapping.second;
CountVisitor count_visitor;
count_visitor.count = 0;
IterateJSFunctions(shared_info, &count_visitor);
int size = count_visitor.count;
Handle<FixedArray> result = isolate->factory()->NewFixedArray(size);
if (size > 0) {
CollectVisitor collect_visitor(result);
IterateJSFunctions(shared_info, &collect_visitor);
} }
return result;
} }
}
class CountVisitor { class CollectFunctionLiterals final
public: : public AstTraversalVisitor<CollectFunctionLiterals> {
void visit(JSFunction* fun) { public:
count++; CollectFunctionLiterals(Isolate* isolate, AstNode* root)
} : AstTraversalVisitor<CollectFunctionLiterals>(isolate, root) {}
int count; void VisitFunctionLiteral(FunctionLiteral* lit) {
}; AstTraversalVisitor::VisitFunctionLiteral(lit);
literals_->push_back(lit);
}
void Run(std::vector<FunctionLiteral*>* literals) {
literals_ = literals;
AstTraversalVisitor::Run();
literals_ = nullptr;
}
class CollectVisitor { private:
public: std::vector<FunctionLiteral*>* literals_;
explicit CollectVisitor(Handle<FixedArray> output) };
: m_output(output), m_pos(0) {}
void visit(JSFunction* fun) { bool ParseScript(Isolate* isolate, ParseInfo* parse_info, bool compile_as_well,
m_output->set(m_pos, fun); std::vector<FunctionLiteral*>* literals,
m_pos++; 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);
} }
private: }
Handle<FixedArray> m_output; if (!success) {
int m_pos; 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(std::make_pair(script_id, literal->function_literal_id()),
FunctionData{literal, should_restart});
}
void LiveEdit::ReplaceFunctionCode( bool Lookup(SharedFunctionInfo* sfi, FunctionData** data) {
Handle<JSArray> new_compile_info_array, if (!sfi->script()->IsScript() || sfi->function_literal_id() == -1) {
Handle<JSArray> shared_info_array) { return false;
Isolate* isolate = new_compile_info_array->GetIsolate();
FunctionInfoWrapper compile_info_wrapper(new_compile_info_array);
SharedInfoWrapper shared_info_wrapper(shared_info_array);
Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
Handle<SharedFunctionInfo> new_shared_info =
compile_info_wrapper.GetSharedFunctionInfo();
if (shared_info->is_compiled()) {
if (shared_info->HasBreakInfo()) {
// Existing break points will be re-applied. Reset the debug info here.
isolate->debug()->RemoveBreakInfoAndMaybeFree(
handle(shared_info->GetDebugInfo(), isolate));
} }
Script* script = Script::cast(sfi->script());
// Clear old bytecode. This will trigger self-healing if we do not install return Lookup(script->id(), sfi->function_literal_id(), data);
// new bytecode. }
shared_info->FlushCompiled();
if (new_shared_info->HasInterpreterData()) { bool Lookup(Handle<Script> script, FunctionLiteral* literal,
shared_info->set_interpreter_data(new_shared_info->interpreter_data()); FunctionData** data) {
} else { return Lookup(script->id(), literal->function_literal_id(), data);
shared_info->set_bytecode_array(new_shared_info->GetBytecodeArray()); }
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();
}
} }
shared_info->set_scope_info(new_shared_info->scope_info()); isolate->thread_manager()->IterateArchivedThreads(this);
shared_info->set_feedback_metadata(new_shared_info->feedback_metadata());
shared_info->DisableOptimization(BailoutReason::kLiveEdit);
} else {
// There should not be any feedback metadata. Keep the outer scope info the
// same.
DCHECK(!shared_info->HasFeedbackMetadata());
} }
int start_position = compile_info_wrapper.GetStartPosition(); private:
int end_position = compile_info_wrapper.GetEndPosition(); bool Lookup(int script_id, int function_literal_id, FunctionData** data) {
// TODO(cbruni): only store position information on the SFI. auto it = map_.find(std::make_pair(script_id, function_literal_id));
shared_info->set_raw_start_position(start_position); if (it == map_.end()) return false;
shared_info->set_raw_end_position(end_position); *data = &it->second;
if (shared_info->scope_info()->HasPositionInfo()) { return true;
shared_info->scope_info()->SetPositionInfo(start_position, end_position); }
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;
}
}
} }
FeedbackVectorFixer::PatchFeedbackVector(&compile_info_wrapper, shared_info, using UniqueLiteralId = std::pair<int, int>; // script_id + literal_id
isolate); std::map<UniqueLiteralId, FunctionData> map_;
};
isolate->debug()->DeoptimizeFunction(shared_info);
}
void LiveEdit::FunctionSourceUpdated(Handle<JSArray> shared_info_array,
int new_function_literal_id) {
SharedInfoWrapper shared_info_wrapper(shared_info_array);
Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
shared_info->set_function_literal_id(new_function_literal_id);
shared_info_array->GetIsolate()->debug()->DeoptimizeFunction(shared_info);
}
void LiveEdit::FixupScript(Isolate* isolate, Handle<Script> script, bool CanPatchScript(const LiteralMap& changed, Handle<Script> script,
int max_function_literal_id) { Handle<Script> new_script,
Handle<WeakFixedArray> old_infos(script->shared_function_infos(), isolate); FunctionDataMap& function_data_map,
Handle<WeakFixedArray> new_infos( debug::LiveEditResult* result) {
isolate->factory()->NewWeakFixedArray(max_function_literal_id + 1)); debug::LiveEditResult::Status status = debug::LiveEditResult::OK;
script->set_shared_function_infos(*new_infos); for (const auto& mapping : changed) {
SharedFunctionInfo::ScriptIterator iterator(isolate, old_infos); FunctionData* data = nullptr;
while (SharedFunctionInfo* shared = iterator.Next()) { function_data_map.Lookup(script, mapping.first, &data);
// We can't use SharedFunctionInfo::SetScript(info, undefined_value()) here, FunctionData* new_data = nullptr;
// as we severed the link from the Script to the SharedFunctionInfo above. function_data_map.Lookup(new_script, mapping.second, &new_data);
Handle<SharedFunctionInfo> info(shared, isolate); Handle<SharedFunctionInfo> sfi;
info->set_script(isolate->heap()->undefined_value()); if (!data->shared.ToHandle(&sfi)) {
Handle<Object> new_noscript_list = FixedArrayOfWeakCells::Add( continue;
isolate, isolate->factory()->noscript_shared_function_infos(), info); } else if (!data->should_restart) {
isolate->heap()->SetRootNoScriptSharedFunctionInfos(*new_noscript_list); UNREACHABLE();
} else if (data->stack_position == FunctionData::ABOVE_BREAK_FRAME) {
// Put the SharedFunctionInfo at its new, correct location. status = debug::LiveEditResult::BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME;
SharedFunctionInfo::SetScript(info, script); } 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;
} }
void LiveEdit::SetFunctionScript(Handle<JSValue> function_wrapper, bool CanRestartFrame(Isolate* isolate, Address fp,
Handle<Object> script_handle) { FunctionDataMap& function_data_map,
Handle<SharedFunctionInfo> shared_info = const LiteralMap& changed, debug::LiveEditResult* result) {
UnwrapSharedFunctionInfoFromJSValue(function_wrapper); DCHECK_GT(fp, 0);
Isolate* isolate = function_wrapper->GetIsolate(); StackFrame* restart_frame = nullptr;
CHECK(script_handle->IsScript() || script_handle->IsUndefined(isolate)); StackFrameIterator it(isolate);
SharedFunctionInfo::SetScript(shared_info, script_handle); for (; !it.done(); it.Advance()) {
shared_info->DisableOptimization(BailoutReason::kLiveEdit); if (it.frame()->fp() == fp) {
restart_frame = it.frame();
isolate->compilation_cache()->Remove(shared_info);
}
namespace {
// For a script text change (defined as position_change_array), translates
// position in unchanged text to position in changed text.
// Text change is a set of non-overlapping regions in text, that have changed
// their contents and length. It is specified as array of groups of 3 numbers:
// (change_begin, change_end, change_end_new_position).
// Each group describes a change in text; groups are sorted by change_begin.
// Only position in text beyond any changes may be successfully translated.
// If a positions is inside some region that changed, result is currently
// undefined.
static int TranslatePosition2(int original_position,
Handle<JSArray> position_change_array) {
int position_diff = 0;
int array_len = GetArrayLength(position_change_array);
Isolate* isolate = position_change_array->GetIsolate();
// TODO(635): binary search may be used here
for (int i = 0; i < array_len; i += 3) {
HandleScope scope(isolate);
Handle<Object> element =
JSReceiver::GetElement(isolate, position_change_array, i)
.ToHandleChecked();
CHECK(element->IsSmi());
int chunk_start = Handle<Smi>::cast(element)->value();
if (original_position < chunk_start) {
break; break;
} }
element = JSReceiver::GetElement(isolate, position_change_array, i + 1) }
.ToHandleChecked(); DCHECK(restart_frame && restart_frame->is_java_script());
CHECK(element->IsSmi()); if (!LiveEdit::kFrameDropperSupported) {
int chunk_end = Handle<Smi>::cast(element)->value(); result->status = debug::LiveEditResult::FRAME_RESTART_IS_NOT_SUPPORTED;
// Position mustn't be inside a chunk. return false;
DCHECK(original_position >= chunk_end); }
element = JSReceiver::GetElement(isolate, position_change_array, i + 2) std::vector<Handle<SharedFunctionInfo>> sfis;
.ToHandleChecked(); JavaScriptFrame::cast(restart_frame)->GetFunctions(&sfis);
CHECK(element->IsSmi()); for (auto& sfi : sfis) {
int chunk_changed_end = Handle<Smi>::cast(element)->value(); FunctionData* data = nullptr;
position_diff = chunk_changed_end - chunk_end; if (!function_data_map.Lookup(*sfi, &data)) continue;
} auto new_literal_it = changed.find(data->literal);
if (new_literal_it == changed.end()) continue;
return original_position + position_diff; 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(Handle<BytecodeArray> code, void TranslateSourcePositionTable(Handle<BytecodeArray> code,
Handle<JSArray> position_change_array) { const std::vector<SourceChangeRange>& diffs) {
Isolate* isolate = position_change_array->GetIsolate(); Isolate* isolate = code->GetIsolate();
SourcePositionTableBuilder builder; SourcePositionTableBuilder builder;
Handle<ByteArray> source_position_table(code->SourcePositionTable(), isolate); Handle<ByteArray> source_position_table(code->SourcePositionTable(), isolate);
...@@ -952,7 +953,7 @@ void TranslateSourcePositionTable(Handle<BytecodeArray> code, ...@@ -952,7 +953,7 @@ void TranslateSourcePositionTable(Handle<BytecodeArray> code,
!iterator.done(); iterator.Advance()) { !iterator.done(); iterator.Advance()) {
SourcePosition position = iterator.source_position(); SourcePosition position = iterator.source_position();
position.SetScriptOffset( position.SetScriptOffset(
TranslatePosition2(position.ScriptOffset(), position_change_array)); LiveEdit::TranslatePosition(diffs, position.ScriptOffset()));
builder.AddPosition(iterator.code_offset(), position, builder.AddPosition(iterator.code_offset(), position,
iterator.is_statement()); iterator.is_statement());
} }
...@@ -964,470 +965,183 @@ void TranslateSourcePositionTable(Handle<BytecodeArray> code, ...@@ -964,470 +965,183 @@ void TranslateSourcePositionTable(Handle<BytecodeArray> code,
CodeLinePosInfoRecordEvent(code->GetFirstBytecodeAddress(), CodeLinePosInfoRecordEvent(code->GetFirstBytecodeAddress(),
*new_source_position_table)); *new_source_position_table));
} }
} // namespace
void LiveEdit::PatchFunctionPositions(Handle<JSArray> shared_info_array,
Handle<JSArray> position_change_array) {
SharedInfoWrapper shared_info_wrapper(shared_info_array);
Handle<SharedFunctionInfo> info = shared_info_wrapper.GetInfo();
int old_function_start = info->StartPosition();
int new_function_start =
TranslatePosition2(old_function_start, position_change_array);
int new_function_end =
TranslatePosition2(info->EndPosition(), position_change_array);
int new_function_token_pos = TranslatePosition2(
info->function_token_position(), position_change_array);
info->set_raw_start_position(new_function_start);
info->set_raw_end_position(new_function_end);
// TODO(cbruni): Allocate helper ScopeInfo once the position fields are gone
// on the SFI.
if (info->scope_info()->HasPositionInfo()) {
info->scope_info()->SetPositionInfo(new_function_start, new_function_end);
}
info->set_function_token_position(new_function_token_pos);
if (info->HasBytecodeArray()) {
TranslateSourcePositionTable(
handle(info->GetBytecodeArray(), shared_info_wrapper.isolate()),
position_change_array);
}
if (info->HasBreakInfo()) {
// Existing break points will be re-applied. Reset the debug info here.
shared_info_wrapper.isolate()->debug()->RemoveBreakInfoAndMaybeFree(
handle(info->GetDebugInfo(), shared_info_wrapper.isolate()));
}
}
static Handle<Script> CreateScriptCopy(Isolate* isolate, void UpdatePositions(Isolate* isolate, Handle<SharedFunctionInfo> sfi,
Handle<Script> original) { const std::vector<SourceChangeRange>& diffs) {
Handle<String> original_source(String::cast(original->source()), isolate); int old_start_position = sfi->StartPosition();
Handle<Script> copy = isolate->factory()->NewScript(original_source); int new_start_position =
LiveEdit::TranslatePosition(diffs, old_start_position);
copy->set_name(original->name()); int new_end_position = LiveEdit::TranslatePosition(diffs, sfi->EndPosition());
copy->set_line_offset(original->line_offset()); int new_function_token_position =
copy->set_column_offset(original->column_offset()); LiveEdit::TranslatePosition(diffs, sfi->function_token_position());
copy->set_type(original->type()); sfi->set_raw_start_position(new_start_position);
copy->set_context_data(original->context_data()); sfi->set_raw_end_position(new_end_position);
copy->set_eval_from_shared_or_wrapped_arguments( sfi->set_function_token_position(new_function_token_position);
original->eval_from_shared_or_wrapped_arguments()); if (sfi->scope_info()->HasPositionInfo()) {
copy->set_eval_from_position(original->eval_from_position()); sfi->scope_info()->SetPositionInfo(new_start_position, new_end_position);
}
Handle<WeakFixedArray> infos(isolate->factory()->NewWeakFixedArray( if (sfi->HasBytecodeArray()) {
original->shared_function_infos()->length())); TranslateSourcePositionTable(handle(sfi->GetBytecodeArray(), isolate),
copy->set_shared_function_infos(*infos); diffs);
}
// Copy all the flags, but clear compilation state.
copy->set_flags(original->flags());
copy->set_compilation_state(Script::COMPILATION_STATE_INITIAL);
return copy;
} }
} // anonymous namespace
Handle<Object> LiveEdit::ChangeScriptSource(Isolate* isolate, void LiveEdit::PatchScript(Isolate* isolate, Handle<Script> script,
Handle<Script> original_script, Handle<String> new_source, bool preview,
Handle<String> new_source, debug::LiveEditResult* result) {
Handle<Object> old_script_name) { std::vector<SourceChangeRange> diffs;
Handle<Object> old_script_object; LiveEdit::CompareStrings(isolate,
if (old_script_name->IsString()) { handle(String::cast(script->source()), isolate),
Handle<Script> old_script = CreateScriptCopy(isolate, original_script); new_source, &diffs);
old_script->set_name(String::cast(*old_script_name)); if (diffs.empty()) {
old_script_object = old_script; result->status = debug::LiveEditResult::OK;
isolate->debug()->OnAfterCompile(old_script); return;
} else {
old_script_object = isolate->factory()->null_value();
} }
original_script->set_source(*new_source); ParseInfo parse_info(isolate, script);
std::vector<FunctionLiteral*> literals;
// Drop line ends so that they will be recalculated. if (!ParseScript(isolate, &parse_info, false, &literals, result)) return;
original_script->set_line_ends(isolate->heap()->undefined_value());
return old_script_object; Handle<Script> new_script = isolate->factory()->CloneScript(script);
} new_script->set_source(*new_source);
std::vector<FunctionLiteral*> new_literals;
void LiveEdit::ReplaceRefToNestedFunction( ParseInfo new_parse_info(isolate, new_script);
Heap* heap, Handle<JSValue> parent_function_wrapper, if (!ParseScript(isolate, &new_parse_info, true, &new_literals, result)) {
Handle<JSValue> orig_function_wrapper, return;
Handle<JSValue> subst_function_wrapper) {
Handle<SharedFunctionInfo> parent_shared =
UnwrapSharedFunctionInfoFromJSValue(parent_function_wrapper);
Handle<SharedFunctionInfo> orig_shared =
UnwrapSharedFunctionInfoFromJSValue(orig_function_wrapper);
Handle<SharedFunctionInfo> subst_shared =
UnwrapSharedFunctionInfoFromJSValue(subst_function_wrapper);
for (RelocIterator it(parent_shared->GetCode()); !it.done(); it.next()) {
if (it.rinfo()->rmode() == RelocInfo::EMBEDDED_OBJECT) {
if (it.rinfo()->target_object() == *orig_shared) {
it.rinfo()->set_target_object(heap, *subst_shared);
}
}
} }
}
FunctionLiteralChanges literal_changes;
CalculateFunctionLiteralChanges(literals, diffs, &literal_changes);
// Check an activation against list of functions. If there is a function LiteralMap changed;
// that matches, its status in result array is changed to status argument value. LiteralMap unchanged;
static bool CheckActivation(Handle<JSArray> shared_info_array, MapLiterals(literal_changes, new_literals, &unchanged, &changed);
Handle<JSArray> result,
StackFrame* frame,
LiveEdit::FunctionPatchabilityStatus status) {
if (!frame->is_java_script()) return false;
Handle<JSFunction> function(JavaScriptFrame::cast(frame)->function(), FunctionDataMap function_data_map;
frame->isolate()); for (const auto& mapping : changed) {
function_data_map.AddInterestingLiteral(script->id(), mapping.first, true);
Isolate* isolate = shared_info_array->GetIsolate(); function_data_map.AddInterestingLiteral(new_script->id(), mapping.second,
int len = GetArrayLength(shared_info_array); false);
for (int i = 0; i < len; i++) {
HandleScope scope(isolate);
Handle<Object> element =
JSReceiver::GetElement(isolate, shared_info_array, i).ToHandleChecked();
Handle<JSValue> jsvalue = Handle<JSValue>::cast(element);
Handle<SharedFunctionInfo> shared =
UnwrapSharedFunctionInfoFromJSValue(jsvalue);
if (function->shared() == *shared ||
(function->code()->is_optimized_code() &&
function->code()->Inlines(*shared))) {
SetElementSloppy(result, i, Handle<Smi>(Smi::FromInt(status), isolate));
return true;
}
} }
return false; for (const auto& mapping : unchanged) {
} function_data_map.AddInterestingLiteral(script->id(), mapping.first, false);
// Describes a set of call frames that execute any of listed functions.
// Finding no such frames does not mean error.
class MultipleFunctionTarget {
public:
MultipleFunctionTarget(Handle<JSArray> old_shared_array,
Handle<JSArray> new_shared_array,
Handle<JSArray> result)
: old_shared_array_(old_shared_array),
new_shared_array_(new_shared_array),
result_(result) {}
bool MatchActivation(StackFrame* frame,
LiveEdit::FunctionPatchabilityStatus status) {
return CheckActivation(old_shared_array_, result_, frame, status);
}
const char* GetNotFoundMessage() const { return nullptr; }
bool FrameUsesNewTarget(StackFrame* frame) {
if (!frame->is_java_script()) return false;
JavaScriptFrame* jsframe = JavaScriptFrame::cast(frame);
Handle<SharedFunctionInfo> old_shared(jsframe->function()->shared(),
jsframe->isolate());
Isolate* isolate = jsframe->isolate();
int len = GetArrayLength(old_shared_array_);
// Find corresponding new shared function info and return whether it
// references new.target.
for (int i = 0; i < len; i++) {
HandleScope scope(isolate);
Handle<Object> old_element =
JSReceiver::GetElement(isolate, old_shared_array_, i)
.ToHandleChecked();
if (!old_shared.is_identical_to(UnwrapSharedFunctionInfoFromJSValue(
Handle<JSValue>::cast(old_element)))) {
continue;
}
Handle<Object> new_element =
JSReceiver::GetElement(isolate, new_shared_array_, i)
.ToHandleChecked();
if (new_element->IsUndefined(isolate)) return false;
Handle<SharedFunctionInfo> new_shared =
UnwrapSharedFunctionInfoFromJSValue(
Handle<JSValue>::cast(new_element));
if (new_shared->scope_info()->HasNewTarget()) {
SetElementSloppy(
result_, i,
Handle<Smi>(
Smi::FromInt(
LiveEdit::FUNCTION_BLOCKED_NO_NEW_TARGET_ON_RESTART),
isolate));
return true;
}
return false;
}
return false;
} }
Address restart_frame_fp = 0;
function_data_map.Fill(isolate, &restart_frame_fp);
void set_status(LiveEdit::FunctionPatchabilityStatus status) { if (!CanPatchScript(changed, script, new_script, function_data_map, result)) {
Isolate* isolate = old_shared_array_->GetIsolate(); return;
int len = GetArrayLength(old_shared_array_); }
for (int i = 0; i < len; ++i) { if (restart_frame_fp &&
Handle<Object> old_element = !CanRestartFrame(isolate, restart_frame_fp, function_data_map, changed,
JSReceiver::GetElement(isolate, result_, i).ToHandleChecked(); result)) {
if (!old_element->IsSmi() || return;
Smi::ToInt(*old_element) == LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH) {
SetElementSloppy(result_, i,
Handle<Smi>(Smi::FromInt(status), isolate));
}
}
} }
private: if (preview) {
Handle<JSArray> old_shared_array_; result->status = debug::LiveEditResult::OK;
Handle<JSArray> new_shared_array_; return;
Handle<JSArray> result_; }
};
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;
// Drops all call frame matched by target and all frames above them. isolate->compilation_cache()->Remove(sfi);
template <typename TARGET> isolate->debug()->DeoptimizeFunction(sfi);
static const char* DropActivationsInActiveThreadImpl(Isolate* isolate, if (sfi->HasDebugInfo()) {
TARGET& target, // NOLINT Handle<DebugInfo> debug_info(sfi->GetDebugInfo(), isolate);
bool do_drop) { isolate->debug()->RemoveBreakInfoAndMaybeFree(debug_info);
Debug* debug = isolate->debug();
Zone zone(isolate->allocator(), ZONE_NAME);
Vector<StackFrame*> frames = CreateStackMap(isolate, &zone);
int top_frame_index = -1;
int frame_index = 0;
for (; frame_index < frames.length(); frame_index++) {
StackFrame* frame = frames[frame_index];
if (frame->id() == debug->break_frame_id()) {
top_frame_index = frame_index;
break;
} }
if (target.MatchActivation( UpdatePositions(isolate, sfi, diffs);
frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
// We are still above break_frame. It is not a target frame,
// it is a problem.
return "Debugger mark-up on stack is not found";
}
}
if (top_frame_index == -1) { sfi->set_function_literal_id(mapping.second->function_literal_id());
// We haven't found break frame, but no function is blocking us anyway. sfi->set_script(*new_script);
return target.GetNotFoundMessage(); Handle<WeakFixedArray> new_script_sfis =
} handle(new_script->shared_function_infos(), isolate);
new_script_sfis->Set(mapping.second->function_literal_id(),
HeapObjectReference::Weak(*sfi));
bool target_frame_found = false; if (sfi->HasPreParsedScopeData()) sfi->ClearPreParsedScopeData();
int bottom_js_frame_index = top_frame_index;
bool non_droppable_frame_found = false;
LiveEdit::FunctionPatchabilityStatus non_droppable_reason;
for (; frame_index < frames.length(); frame_index++) { for (auto& js_function : data->js_functions) {
StackFrame* frame = frames[frame_index]; js_function->set_feedback_cell(*isolate->factory()->many_closures_cell());
if (frame->is_exit() || frame->is_builtin_exit()) { if (!js_function->is_compiled()) continue;
non_droppable_frame_found = true; JSFunction::EnsureFeedbackVector(js_function);
non_droppable_reason = LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE;
break;
} }
if (frame->is_java_script()) {
SharedFunctionInfo* shared =
JavaScriptFrame::cast(frame)->function()->shared();
if (IsResumableFunction(shared->kind())) {
non_droppable_frame_found = true;
non_droppable_reason = LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR;
break;
}
}
if (target.MatchActivation(
frame, LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
target_frame_found = true;
bottom_js_frame_index = frame_index;
}
}
if (non_droppable_frame_found) { if (!sfi->HasBytecodeArray()) continue;
// There is a C or generator frame on stack. We can't drop C frames, and we Handle<BytecodeArray> bytecode(sfi->GetBytecodeArray(), isolate);
// can't restart generators. Check that there are no target frames below Handle<FixedArray> constants(bytecode->constant_pool(), isolate);
// them. for (int i = 0; i < constants->length(); ++i) {
for (; frame_index < frames.length(); frame_index++) { if (!constants->get(i)->IsSharedFunctionInfo()) continue;
StackFrame* frame = frames[frame_index]; FunctionData* data = nullptr;
if (frame->is_java_script()) { if (!function_data_map.Lookup(SharedFunctionInfo::cast(constants->get(i)),
if (target.MatchActivation(frame, non_droppable_reason)) { &data)) {
// Fail. continue;
return nullptr;
}
if (non_droppable_reason ==
LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR &&
!target_frame_found) {
// Fail.
target.set_status(non_droppable_reason);
return nullptr;
}
} }
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();
// We cannot restart a frame that uses new.target. if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
if (target.FrameUsesNewTarget(frames[bottom_js_frame_index])) return nullptr; Handle<SharedFunctionInfo> sfi;
if (!data->shared.ToHandle(&sfi)) continue;
if (!do_drop) {
// We are in check-only mode.
return nullptr;
}
if (!target_frame_found) {
// Nothing to drop.
return target.GetNotFoundMessage();
}
if (!LiveEdit::kFrameDropperSupported) {
return "Stack manipulations are not supported in this architecture.";
}
debug->ScheduleFrameRestart(frames[bottom_js_frame_index]);
return nullptr;
}
// Fills result array with statuses of functions. Modifies the stack isolate->debug()->DeoptimizeFunction(sfi);
// removing all listed function if possible and if do_drop is true. isolate->compilation_cache()->Remove(sfi);
static const char* DropActivationsInActiveThread( for (auto& js_function : data->js_functions) {
Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array, js_function->set_shared(*new_sfi);
Handle<JSArray> result, bool do_drop) { js_function->set_code(js_function->shared()->GetCode());
MultipleFunctionTarget target(old_shared_array, new_shared_array, result);
Isolate* isolate = old_shared_array->GetIsolate();
const char* message = js_function->set_feedback_cell(*isolate->factory()->many_closures_cell());
DropActivationsInActiveThreadImpl(isolate, target, do_drop); if (!js_function->is_compiled()) continue;
if (message) { JSFunction::EnsureFeedbackVector(js_function);
return message;
}
int array_len = GetArrayLength(old_shared_array);
// Replace "blocked on active" with "replaced on active" status.
for (int i = 0; i < array_len; i++) {
Handle<Object> obj =
JSReceiver::GetElement(isolate, result, i).ToHandleChecked();
if (*obj == Smi::FromInt(LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
Handle<Object> replaced(
Smi::FromInt(LiveEdit::FUNCTION_REPLACED_ON_ACTIVE_STACK), isolate);
SetElementSloppy(result, i, replaced);
} }
} }
return nullptr;
}
bool LiveEdit::FindActiveGenerators(Isolate* isolate,
Handle<FixedArray> shared_info_array,
Handle<FixedArray> result, int len) {
bool found_suspended_activations = false;
DCHECK_LE(len, result->length());
FunctionPatchabilityStatus active = FUNCTION_BLOCKED_ACTIVE_GENERATOR;
Heap* heap = isolate->heap(); if (restart_frame_fp) {
HeapIterator iterator(heap, HeapIterator::kFilterUnreachable); for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
HeapObject* obj = nullptr; if (it.frame()->fp() == restart_frame_fp) {
while ((obj = iterator.next()) != nullptr) { isolate->debug()->ScheduleFrameRestart(it.frame());
if (!obj->IsJSGeneratorObject()) continue; result->stack_changed = true;
break;
JSGeneratorObject* gen = JSGeneratorObject::cast(obj);
if (gen->is_closed()) continue;
HandleScope scope(isolate);
for (int i = 0; i < len; i++) {
Handle<JSValue> jsvalue = Handle<JSValue>::cast(
FixedArray::get(*shared_info_array, i, isolate));
Handle<SharedFunctionInfo> shared =
UnwrapSharedFunctionInfoFromJSValue(jsvalue);
if (gen->function()->shared() == *shared) {
result->set(i, Smi::FromInt(active));
found_suspended_activations = true;
} }
} }
} }
return found_suspended_activations; 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) {
class InactiveThreadActivationsChecker : public ThreadVisitor { debug->thread_local_.restart_fp_ = 0;
public:
InactiveThreadActivationsChecker(Handle<JSArray> old_shared_array,
Handle<JSArray> result)
: old_shared_array_(old_shared_array),
result_(result),
has_blocked_functions_(false) {}
void VisitThread(Isolate* isolate, ThreadLocalTop* top) {
for (StackFrameIterator it(isolate, top); !it.done(); it.Advance()) {
has_blocked_functions_ |=
CheckActivation(old_shared_array_, result_, it.frame(),
LiveEdit::FUNCTION_BLOCKED_ON_OTHER_STACK);
}
}
bool HasBlockedFunctions() {
return has_blocked_functions_;
}
private:
Handle<JSArray> old_shared_array_;
Handle<JSArray> result_;
bool has_blocked_functions_;
};
Handle<JSArray> LiveEdit::CheckAndDropActivations(
Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array,
bool do_drop) {
Isolate* isolate = old_shared_array->GetIsolate();
int len = GetArrayLength(old_shared_array);
DCHECK(old_shared_array->HasFastElements());
Handle<FixedArray> old_shared_array_elements(
FixedArray::cast(old_shared_array->elements()), isolate);
Handle<JSArray> result = isolate->factory()->NewJSArray(len);
result->set_length(Smi::FromInt(len));
JSObject::EnsureWritableFastElements(result);
Handle<FixedArray> result_elements =
handle(FixedArray::cast(result->elements()), isolate);
// Fill the default values.
for (int i = 0; i < len; i++) {
FunctionPatchabilityStatus status = FUNCTION_AVAILABLE_FOR_PATCH;
result_elements->set(i, Smi::FromInt(status));
}
// Scan the heap for active generators -- those that are either currently
// running (as we wouldn't want to restart them, because we don't know where
// to restart them from) or suspended. Fail if any one corresponds to the set
// of functions being edited.
if (FindActiveGenerators(isolate, old_shared_array_elements, result_elements,
len)) {
return result;
}
// Check inactive threads. Fail if some functions are blocked there.
InactiveThreadActivationsChecker inactive_threads_checker(old_shared_array,
result);
isolate->thread_manager()->IterateArchivedThreads(
&inactive_threads_checker);
if (inactive_threads_checker.HasBlockedFunctions()) {
return result;
}
// Try to drop activations from the current stack.
const char* error_message = DropActivationsInActiveThread(
old_shared_array, new_shared_array, result, do_drop);
if (error_message != nullptr) {
// Add error message as an array extra element.
Handle<String> str =
isolate->factory()->NewStringFromAsciiChecked(error_message);
SetElementSloppy(result, len, str);
}
return result;
} }
bool LiveEdit::RestartFrame(JavaScriptFrame* frame) { bool LiveEdit::RestartFrame(JavaScriptFrame* frame) {
if (!LiveEdit::kFrameDropperSupported) return false; if (!LiveEdit::kFrameDropperSupported) return false;
Isolate* isolate = frame->isolate(); Isolate* isolate = frame->isolate();
Zone zone(isolate->allocator(), ZONE_NAME);
Vector<StackFrame*> frames = CreateStackMap(isolate, &zone);
StackFrame::Id break_frame_id = isolate->debug()->break_frame_id(); StackFrame::Id break_frame_id = isolate->debug()->break_frame_id();
bool break_frame_found = break_frame_id == StackFrame::NO_ID; bool break_frame_found = break_frame_id == StackFrame::NO_ID;
for (StackFrame* current : frames) { for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
StackFrame* current = it.frame();
break_frame_found = break_frame_found || break_frame_id == current->id(); break_frame_found = break_frame_found || break_frame_id == current->id();
if (current->fp() == frame->fp()) { if (current->fp() == frame->fp()) {
if (break_frame_found) { if (break_frame_found) {
...@@ -1453,95 +1167,9 @@ bool LiveEdit::RestartFrame(JavaScriptFrame* frame) { ...@@ -1453,95 +1167,9 @@ bool LiveEdit::RestartFrame(JavaScriptFrame* frame) {
return false; return false;
} }
Handle<JSArray> LiveEditFunctionTracker::Collect(FunctionLiteral* node,
Handle<Script> script,
Zone* zone, Isolate* isolate) {
LiveEditFunctionTracker visitor(script, zone, isolate);
visitor.VisitFunctionLiteral(node);
return visitor.result_;
}
LiveEditFunctionTracker::LiveEditFunctionTracker(Handle<Script> script,
Zone* zone, Isolate* isolate)
: AstTraversalVisitor<LiveEditFunctionTracker>(isolate) {
current_parent_index_ = -1;
isolate_ = isolate;
len_ = 0;
result_ = isolate->factory()->NewJSArray(10);
script_ = script;
zone_ = zone;
}
void LiveEditFunctionTracker::VisitFunctionLiteral(FunctionLiteral* node) {
// FunctionStarted is called in pre-order.
FunctionStarted(node);
// Recurse using the regular traversal.
AstTraversalVisitor::VisitFunctionLiteral(node);
// FunctionDone are called in post-order.
Handle<SharedFunctionInfo> info =
script_->FindSharedFunctionInfo(isolate_, node).ToHandleChecked();
FunctionDone(info, node->scope());
}
void LiveEditFunctionTracker::FunctionStarted(FunctionLiteral* fun) {
HandleScope handle_scope(isolate_);
FunctionInfoWrapper info = FunctionInfoWrapper::Create(isolate_);
info.SetInitialProperties(fun->name(isolate_), fun->start_position(),
fun->end_position(), fun->parameter_count(),
current_parent_index_, fun->function_literal_id());
current_parent_index_ = len_;
SetElementSloppy(result_, len_, info.GetJSArray());
len_++;
}
// Saves full information about a function: its code, its scope info
// and a SharedFunctionInfo object.
void LiveEditFunctionTracker::FunctionDone(Handle<SharedFunctionInfo> shared,
Scope* scope) {
HandleScope handle_scope(isolate_);
FunctionInfoWrapper info = FunctionInfoWrapper::cast(
*JSReceiver::GetElement(isolate_, result_, current_parent_index_)
.ToHandleChecked());
info.SetSharedFunctionInfo(isolate_, shared);
Handle<Object> scope_info_list = SerializeFunctionScope(scope);
info.SetFunctionScopeInfo(scope_info_list);
current_parent_index_ = info.GetParentIndex();
}
Handle<Object> LiveEditFunctionTracker::SerializeFunctionScope(Scope* scope) {
Handle<JSArray> scope_info_list = isolate_->factory()->NewJSArray(10);
int scope_info_length = 0;
// Saves some description of scope. It stores name and indexes of
// variables in the whole scope chain. Null-named slots delimit
// scopes of this chain.
Scope* current_scope = scope;
while (current_scope != nullptr) {
HandleScope handle_scope(isolate_);
for (Variable* var : *current_scope->locals()) {
if (!var->IsContextSlot()) continue;
int context_index = var->index() - Context::MIN_CONTEXT_SLOTS;
int location = scope_info_length + context_index * 2;
SetElementSloppy(scope_info_list, location, var->name());
SetElementSloppy(scope_info_list, location + 1,
handle(Smi::FromInt(var->index()), isolate_));
}
scope_info_length += current_scope->ContextLocalCount() * 2;
SetElementSloppy(scope_info_list, scope_info_length,
isolate_->factory()->null_value());
scope_info_length++;
current_scope = current_scope->outer_scope();
}
return scope_info_list;
}
void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1, void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1,
Handle<String> s2, Handle<String> s2,
std::vector<SourceChangeRange>* changes) { std::vector<SourceChangeRange>* diffs) {
s1 = String::Flatten(isolate, s1); s1 = String::Flatten(isolate, s1);
s2 = String::Flatten(isolate, s2); s2 = String::Flatten(isolate, s2);
...@@ -1550,32 +1178,26 @@ void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1, ...@@ -1550,32 +1178,26 @@ void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1,
LineArrayCompareInput input(s1, s2, line_ends1, line_ends2); LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
TokenizingLineArrayCompareOutput output(isolate, line_ends1, line_ends2, s1, TokenizingLineArrayCompareOutput output(isolate, line_ends1, line_ends2, s1,
s2, changes); s2, diffs);
NarrowDownInput(&input, &output); NarrowDownInput(&input, &output);
Comparator::CalculateDifference(&input, &output); Comparator::CalculateDifference(&input, &output);
} }
int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& changes, int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& diffs,
int position) { int position) {
auto it = std::lower_bound(changes.begin(), changes.end(), position, auto it = std::lower_bound(diffs.begin(), diffs.end(), position,
[](const SourceChangeRange& change, int position) { [](const SourceChangeRange& change, int position) {
return change.end_position < position; return change.end_position < position;
}); });
if (it != changes.end() && position == it->end_position) { if (it != diffs.end() && position == it->end_position) {
return it->new_end_position; return it->new_end_position;
} }
if (it == changes.begin()) return position; if (it == diffs.begin()) return position;
DCHECK(it == changes.end() || position <= it->start_position); DCHECK(it == diffs.end() || position <= it->start_position);
it = std::prev(it); it = std::prev(it);
return position + (it->new_end_position - it->end_position); return position + (it->new_end_position - it->end_position);
} }
void LiveEdit::PatchScript(Handle<Script> script, Handle<String> new_source,
debug::LiveEditResult* result) {
script->GetIsolate()->debug()->SetScriptSource(script, new_source, false,
result);
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -5,28 +5,10 @@ ...@@ -5,28 +5,10 @@
#ifndef V8_DEBUG_LIVEEDIT_H_ #ifndef V8_DEBUG_LIVEEDIT_H_
#define V8_DEBUG_LIVEEDIT_H_ #define V8_DEBUG_LIVEEDIT_H_
#include <vector>
// Live Edit feature implementation. #include "src/globals.h"
// User should be able to change script on already running VM. This feature #include "src/handles.h"
// matches hot swap features in other frameworks.
//
// The basic use-case is when user spots some mistake in function body
// from debugger and wishes to change the algorithm without restart.
//
// A single change always has a form of a simple replacement (in pseudo-code):
// script.source[positions, positions+length] = new_string;
// Implementation first determines, which function's body includes this
// change area. Then both old and new versions of script are fully compiled
// in order to analyze, whether the function changed its outer scope
// expectations (or number of parameters). If it didn't, function's code is
// patched with a newly compiled code. If it did change, enclosing function
// gets patched. All inner functions are left untouched, whatever happened
// to them in a new script version. However, new version of code will
// instantiate newly compiled functions.
#include "src/allocation.h"
#include "src/ast/ast-traversal-visitor.h"
namespace v8 { namespace v8 {
namespace debug { namespace debug {
...@@ -34,6 +16,9 @@ struct LiveEditResult; ...@@ -34,6 +16,9 @@ struct LiveEditResult;
} }
namespace internal { namespace internal {
class Script;
class String;
class Debug;
class JavaScriptFrame; class JavaScriptFrame;
struct SourceChangeRange { struct SourceChangeRange {
...@@ -43,266 +28,50 @@ struct SourceChangeRange { ...@@ -43,266 +28,50 @@ struct SourceChangeRange {
int new_end_position; int new_end_position;
}; };
// This class collects some specific information on structure of functions /**
// in a particular script. Liveedit step-by-step:
// 1. calculate diff between old source and new source,
// The primary interest of the Tracker is to record function scope structures 2. map function literals from old source to new source,
// in order to analyze whether function code may be safely patched (with new 3. create new script for new_source,
// code successfully reading existing data from function scopes). The Tracker 4. mark literals with changed code as changed, all others as unchanged,
// also collects compiled function codes. 5. check that for changed literals there are no:
class LiveEditFunctionTracker - running generators in the heap,
: public AstTraversalVisitor<LiveEditFunctionTracker> { - non droppable frames (e.g. running generator) above them on stack.
public: 6. mark the bottom most frame with changed function as scheduled for restart
// Traverses the entire AST, and records information about all if any,
// FunctionLiterals for further use by LiveEdit code patching. The collected 7. for unchanged functions:
// information is returned as a serialized array. - deoptimize,
static Handle<JSArray> Collect(FunctionLiteral* node, Handle<Script> script, - remove from cache,
Zone* zone, Isolate* isolate); - update source positions,
- move to new script,
protected: - reset feedback information and preparsed scope information if any,
friend AstTraversalVisitor<LiveEditFunctionTracker>; - replace any sfi in constant pool with changed one if any.
void VisitFunctionLiteral(FunctionLiteral* node); 8. for changed functions:
- deoptimize
private: - remove from cache,
LiveEditFunctionTracker(Handle<Script> script, Zone* zone, Isolate* isolate); - reset feedback information,
- update all links from js functions to old shared with new one.
void FunctionStarted(FunctionLiteral* fun); 9. swap scripts.
void FunctionDone(Handle<SharedFunctionInfo> shared, Scope* scope); */
Handle<Object> SerializeFunctionScope(Scope* scope);
Handle<Script> script_;
Zone* zone_;
Isolate* isolate_;
Handle<JSArray> result_;
int len_;
int current_parent_index_;
DISALLOW_COPY_AND_ASSIGN(LiveEditFunctionTracker);
};
class LiveEdit : AllStatic { class LiveEdit : AllStatic {
public: public:
static void InitializeThreadLocal(Debug* debug); static void InitializeThreadLocal(Debug* debug);
V8_WARN_UNUSED_RESULT static MaybeHandle<JSArray> GatherCompileInfo(
Isolate* isolate, Handle<Script> script, Handle<String> source);
static void ReplaceFunctionCode(Handle<JSArray> new_compile_info_array,
Handle<JSArray> shared_info_array);
static void FixupScript(Isolate* isolate, Handle<Script> script,
int max_function_literal_id);
static void FunctionSourceUpdated(Handle<JSArray> shared_info_array,
int new_function_literal_id);
// Updates script field in FunctionSharedInfo.
static void SetFunctionScript(Handle<JSValue> function_wrapper,
Handle<Object> script_handle);
static void PatchFunctionPositions(Handle<JSArray> shared_info_array,
Handle<JSArray> position_change_array);
// For a script updates its source field. If old_script_name is provided
// (i.e. is a String), also creates a copy of the script with its original
// source and sends notification to debugger.
static Handle<Object> ChangeScriptSource(Isolate* isolate,
Handle<Script> original_script,
Handle<String> new_source,
Handle<Object> old_script_name);
// In a code of a parent function replaces original function as embedded
// object with a substitution one.
static void ReplaceRefToNestedFunction(Heap* heap,
Handle<JSValue> parent_function_shared,
Handle<JSValue> orig_function_shared,
Handle<JSValue> subst_function_shared);
// Find open generator activations, and set corresponding "result" elements to
// FUNCTION_BLOCKED_ACTIVE_GENERATOR.
static bool FindActiveGenerators(Isolate* isolate,
Handle<FixedArray> shared_info_array,
Handle<FixedArray> result, int len);
// Checks listed functions on stack and return array with corresponding
// FunctionPatchabilityStatus statuses; extra array element may
// contain general error message. Modifies the current stack and
// has restart the lowest found frames and drops all other frames above
// if possible and if do_drop is true.
static Handle<JSArray> CheckAndDropActivations(
Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array,
bool do_drop);
// Restarts the call frame and completely drops all frames above it. // Restarts the call frame and completely drops all frames above it.
static bool RestartFrame(JavaScriptFrame* frame); static bool RestartFrame(JavaScriptFrame* frame);
static void CompareStrings(Isolate* isolate, Handle<String> a, static void CompareStrings(Isolate* isolate, Handle<String> a,
Handle<String> b, Handle<String> b,
std::vector<SourceChangeRange>* changes); std::vector<SourceChangeRange>* diffs);
static int TranslatePosition(const std::vector<SourceChangeRange>& changed, static int TranslatePosition(const std::vector<SourceChangeRange>& changed,
int position); int position);
static void PatchScript(Handle<Script> script, Handle<String> source, static void PatchScript(Isolate* isolate, Handle<Script> script,
Handle<String> source, bool preview,
debug::LiveEditResult* result); debug::LiveEditResult* result);
// A copy of this is in liveedit.js.
enum FunctionPatchabilityStatus {
FUNCTION_AVAILABLE_FOR_PATCH = 1,
FUNCTION_BLOCKED_ON_ACTIVE_STACK = 2,
FUNCTION_BLOCKED_ON_OTHER_STACK = 3,
FUNCTION_BLOCKED_UNDER_NATIVE_CODE = 4,
FUNCTION_REPLACED_ON_ACTIVE_STACK = 5,
FUNCTION_BLOCKED_UNDER_GENERATOR = 6,
FUNCTION_BLOCKED_ACTIVE_GENERATOR = 7,
FUNCTION_BLOCKED_NO_NEW_TARGET_ON_RESTART = 8
};
// Compares 2 strings line-by-line, then token-wise and returns diff in form
// of array of triplets (pos1, pos1_end, pos2_end) describing list
// of diff chunks.
static Handle<JSArray> CompareStrings(Isolate* isolate, Handle<String> s1,
Handle<String> s2);
// Architecture-specific constant. // Architecture-specific constant.
static const bool kFrameDropperSupported; static const bool kFrameDropperSupported;
}; };
// Simple helper class that creates more or less typed structures over
// JSArray object. This is an adhoc method of passing structures from C++
// to JavaScript.
template<typename S>
class JSArrayBasedStruct {
public:
static S Create(Isolate* isolate) {
Factory* factory = isolate->factory();
Handle<JSArray> array = factory->NewJSArray(S::kSize_);
return S(array);
}
static S cast(Object* object) {
JSArray* array = JSArray::cast(object);
Handle<JSArray> array_handle(array, array->GetIsolate());
return S(array_handle);
}
explicit JSArrayBasedStruct(Handle<JSArray> array) : array_(array) {
}
Handle<JSArray> GetJSArray() {
return array_;
}
Isolate* isolate() const {
return array_->GetIsolate();
}
protected:
void SetField(int field_position, Handle<Object> value) {
Object::SetElement(isolate(), array_, field_position, value,
LanguageMode::kSloppy)
.Assert();
}
void SetSmiValueField(int field_position, int value) {
SetField(field_position, Handle<Smi>(Smi::FromInt(value), isolate()));
}
Handle<Object> GetField(int field_position) {
return JSReceiver::GetElement(isolate(), array_, field_position)
.ToHandleChecked();
}
int GetSmiValueField(int field_position) {
Handle<Object> res = GetField(field_position);
return Handle<Smi>::cast(res)->value();
}
private:
Handle<JSArray> array_;
};
// Represents some function compilation details. This structure will be used
// from JavaScript. It contains Code object, which is kept wrapped
// into a BlindReference for sanitizing reasons.
class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> {
public:
explicit FunctionInfoWrapper(Handle<JSArray> array)
: JSArrayBasedStruct<FunctionInfoWrapper>(array) {
}
void SetInitialProperties(Handle<String> name, int start_position,
int end_position, int param_num, int parent_index,
int function_literal_id);
void SetFunctionScopeInfo(Handle<Object> scope_info_array) {
this->SetField(kFunctionScopeInfoOffset_, scope_info_array);
}
void SetSharedFunctionInfo(Isolate* isolate, Handle<SharedFunctionInfo> info);
Handle<SharedFunctionInfo> GetSharedFunctionInfo();
int GetParentIndex() {
return this->GetSmiValueField(kParentIndexOffset_);
}
int GetStartPosition() {
return this->GetSmiValueField(kStartPositionOffset_);
}
int GetEndPosition() { return this->GetSmiValueField(kEndPositionOffset_); }
private:
static const int kFunctionNameOffset_ = 0;
static const int kStartPositionOffset_ = 1;
static const int kEndPositionOffset_ = 2;
static const int kParamNumOffset_ = 3;
static const int kFunctionScopeInfoOffset_ = 4;
static const int kParentIndexOffset_ = 5;
static const int kSharedFunctionInfoOffset_ = 6;
static const int kFunctionLiteralIdOffset_ = 7;
static const int kSize_ = 8;
friend class JSArrayBasedStruct<FunctionInfoWrapper>;
};
// Wraps SharedFunctionInfo along with some of its fields for passing it
// back to JavaScript. SharedFunctionInfo object itself is additionally
// wrapped into BlindReference for sanitizing reasons.
class SharedInfoWrapper : public JSArrayBasedStruct<SharedInfoWrapper> {
public:
static bool IsInstance(Handle<JSArray> array) {
if (array->length() != Smi::FromInt(kSize_)) return false;
Handle<Object> element(
JSReceiver::GetElement(array->GetIsolate(), array, kSharedInfoOffset_)
.ToHandleChecked());
if (!element->IsJSValue()) return false;
return Handle<JSValue>::cast(element)->value()->IsSharedFunctionInfo();
}
explicit SharedInfoWrapper(Handle<JSArray> array)
: JSArrayBasedStruct<SharedInfoWrapper>(array) {
}
void SetProperties(Handle<String> name,
int start_position,
int end_position,
Handle<SharedFunctionInfo> info);
Handle<SharedFunctionInfo> GetInfo();
private:
static const int kFunctionNameOffset_ = 0;
static const int kStartPositionOffset_ = 1;
static const int kEndPositionOffset_ = 2;
static const int kSharedInfoOffset_ = 3;
static const int kSize_ = 4;
friend class JSArrayBasedStruct<SharedInfoWrapper>;
};
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
......
// 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.
// LiveEdit feature implementation. The script should be executed after
// debug.js.
// A LiveEdit namespace. It contains functions that modifies JavaScript code
// according to changes of script source (if possible).
//
// When new script source is put in, the difference is calculated textually,
// in form of list of delete/add/change chunks. The functions that include
// change chunk(s) get recompiled, or their enclosing functions are
// recompiled instead.
// If the function may not be recompiled (e.g. it was completely erased in new
// version of the script) it remains unchanged, but the code that could
// create a new instance of this function goes away. An old version of script
// is created to back up this obsolete function.
// All unchanged functions have their positions updated accordingly.
//
// LiveEdit namespace is declared inside a single function constructor.
(function(global, utils) {
"use strict";
// -------------------------------------------------------------------
// Imports
var GlobalArray = global.Array;
var MathFloor = global.Math.floor;
var MathMax = global.Math.max;
var SyntaxError = global.SyntaxError;
// -------------------------------------------------------------------
function FindScriptSourcePosition(script, opt_line, opt_column) {
var location = %ScriptLocationFromLine(script, opt_line, opt_column, 0);
return location ? location.position : null;
};
// Forward declaration for minifier.
var FunctionStatus;
// Applies the change to the script.
// The change is in form of list of chunks encoded in a single array as
// a series of triplets (pos1_start, pos1_end, pos2_end)
function ApplyPatchMultiChunk(script, diff_array, new_source, preview_only,
change_log) {
var old_source = script.source;
// Gather compile information about old version of script.
var old_compile_info = GatherCompileInfo(old_source, script);
// Build tree structures for old and new versions of the script.
var root_old_node = BuildCodeInfoTree(old_compile_info);
var pos_translator = new PosTranslator(diff_array);
// Analyze changes.
MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
// Find all SharedFunctionInfo's that were compiled from this script.
FindLiveSharedInfos(root_old_node, script);
// Gather compile information about new version of script.
var new_compile_info;
try {
new_compile_info = GatherCompileInfo(new_source, script);
} catch (e) {
var failure =
new Failure("Failed to compile new version of script: " + e);
if (e instanceof SyntaxError) {
var details = {
type: "liveedit_compile_error",
syntaxErrorMessage: e.message
};
CopyErrorPositionToDetails(e, details);
failure.details = details;
}
throw failure;
}
var max_function_literal_id = new_compile_info.reduce(
(max, info) => MathMax(max, info.function_literal_id), 0);
var root_new_node = BuildCodeInfoTree(new_compile_info);
// Link recompiled script data with other data.
FindCorrespondingFunctions(root_old_node, root_new_node);
// Prepare to-do lists.
var replace_code_list = new GlobalArray();
var link_to_old_script_list = new GlobalArray();
var link_to_original_script_list = new GlobalArray();
var update_positions_list = new GlobalArray();
function HarvestTodo(old_node) {
function CollectDamaged(node) {
link_to_old_script_list.push(node);
for (var i = 0; i < node.children.length; i++) {
CollectDamaged(node.children[i]);
}
}
// Recursively collects all newly compiled functions that are going into
// business and should have link to the actual script updated.
function CollectNew(node_list) {
for (var i = 0; i < node_list.length; i++) {
link_to_original_script_list.push(node_list[i]);
CollectNew(node_list[i].children);
}
}
if (old_node.status == FunctionStatus.DAMAGED) {
CollectDamaged(old_node);
return;
}
if (old_node.status == FunctionStatus.UNCHANGED) {
update_positions_list.push(old_node);
} else if (old_node.status == FunctionStatus.SOURCE_CHANGED) {
update_positions_list.push(old_node);
} else if (old_node.status == FunctionStatus.CHANGED) {
replace_code_list.push(old_node);
CollectNew(old_node.unmatched_new_nodes);
}
for (var i = 0; i < old_node.children.length; i++) {
HarvestTodo(old_node.children[i]);
}
}
var preview_description = {
change_tree: DescribeChangeTree(root_old_node),
textual_diff: {
old_len: old_source.length,
new_len: new_source.length,
chunks: diff_array
},
updated: false
};
if (preview_only) {
return preview_description;
}
HarvestTodo(root_old_node);
// Collect shared infos for functions whose code need to be patched.
var replaced_function_old_infos = new GlobalArray();
var replaced_function_new_infos = new GlobalArray();
for (var i = 0; i < replace_code_list.length; i++) {
var old_infos = replace_code_list[i].live_shared_function_infos;
var new_info =
replace_code_list[i].corresponding_node.info.shared_function_info;
if (old_infos) {
for (var j = 0; j < old_infos.length; j++) {
replaced_function_old_infos.push(old_infos[j]);
replaced_function_new_infos.push(new_info);
}
}
}
// We haven't changed anything before this line yet.
// Committing all changes.
// Check that function being patched is not currently on stack or drop them.
var dropped_functions_number =
CheckStackActivations(replaced_function_old_infos,
replaced_function_new_infos,
change_log);
// Our current implementation requires client to manually issue "step in"
// command for correct stack state if the stack was modified.
preview_description.stack_modified = dropped_functions_number != 0;
var old_script;
// Create an old script only if there are function that should be linked
// to old version.
if (link_to_old_script_list.length == 0) {
%LiveEditReplaceScript(script, new_source, null);
old_script = UNDEFINED;
} else {
var old_script_name = CreateNameForOldScript(script);
// Update the script text and create a new script representing an old
// version of the script.
old_script = %LiveEditReplaceScript(script, new_source, old_script_name);
var link_to_old_script_report = new GlobalArray();
change_log.push( { linked_to_old_script: link_to_old_script_report } );
// We need to link to old script all former nested functions.
for (var i = 0; i < link_to_old_script_list.length; i++) {
LinkToOldScript(link_to_old_script_list[i], old_script,
link_to_old_script_report);
}
preview_description.created_script_name = old_script_name;
}
for (var i = 0; i < replace_code_list.length; i++) {
PatchFunctionCode(replace_code_list[i], change_log);
}
var position_patch_report = new GlobalArray();
change_log.push( {position_patched: position_patch_report} );
for (var i = 0; i < update_positions_list.length; i++) {
// TODO(LiveEdit): take into account whether it's source_changed or
// unchanged and whether positions changed at all.
PatchPositions(update_positions_list[i], diff_array,
position_patch_report);
if (update_positions_list[i].live_shared_function_infos) {
var new_function_literal_id =
update_positions_list[i]
.corresponding_node.info.function_literal_id;
update_positions_list[i].live_shared_function_infos.forEach(function(
info) {
%LiveEditFunctionSourceUpdated(
info.raw_array, new_function_literal_id);
});
}
}
%LiveEditFixupScript(script, max_function_literal_id);
// Link all the functions we're going to use to an actual script.
for (var i = 0; i < link_to_original_script_list.length; i++) {
%LiveEditFunctionSetScript(
link_to_original_script_list[i].info.shared_function_info, script);
}
preview_description.updated = true;
return preview_description;
}
// Fully compiles source string as a script. Returns Array of
// FunctionCompileInfo -- a descriptions of all functions of the script.
// Elements of array are ordered by start positions of functions (from top
// to bottom) in the source. Fields outer_index and next_sibling_index help
// to navigate the nesting structure of functions.
//
// All functions get compiled linked to script provided as parameter script.
// TODO(LiveEdit): consider not using actual scripts as script, because
// we have to manually erase all links right after compile.
function GatherCompileInfo(source, script) {
// Get function info, elements are partially sorted (it is a tree of
// nested functions serialized as parent followed by serialized children.
var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
// Sort function infos by start position field.
var compile_info = new GlobalArray();
var old_index_map = new GlobalArray();
for (var i = 0; i < raw_compile_info.length; i++) {
var info = new FunctionCompileInfo(raw_compile_info[i]);
// Remove all links to the actual script. Breakpoints system and
// LiveEdit itself believe that any function in heap that points to a
// particular script is a regular function.
// For some functions we will restore this link later.
%LiveEditFunctionSetScript(info.shared_function_info, UNDEFINED);
compile_info.push(info);
old_index_map.push(i);
}
for (var i = 0; i < compile_info.length; i++) {
var k = i;
for (var j = i + 1; j < compile_info.length; j++) {
if (compile_info[k].start_position > compile_info[j].start_position) {
k = j;
}
}
if (k != i) {
var temp_info = compile_info[k];
var temp_index = old_index_map[k];
compile_info[k] = compile_info[i];
old_index_map[k] = old_index_map[i];
compile_info[i] = temp_info;
old_index_map[i] = temp_index;
}
}
// After sorting update outer_index field using old_index_map. Also
// set next_sibling_index field.
var current_index = 0;
// The recursive function, that goes over all children of a particular
// node (i.e. function info).
function ResetIndexes(new_parent_index, old_parent_index) {
var previous_sibling = -1;
while (current_index < compile_info.length &&
compile_info[current_index].outer_index == old_parent_index) {
var saved_index = current_index;
compile_info[saved_index].outer_index = new_parent_index;
if (previous_sibling != -1) {
compile_info[previous_sibling].next_sibling_index = saved_index;
}
previous_sibling = saved_index;
current_index++;
ResetIndexes(saved_index, old_index_map[saved_index]);
}
if (previous_sibling != -1) {
compile_info[previous_sibling].next_sibling_index = -1;
}
}
ResetIndexes(-1, -1);
Assert(current_index == compile_info.length);
return compile_info;
}
// Replaces function's Code.
function PatchFunctionCode(old_node, change_log) {
var new_info = old_node.corresponding_node.info;
if (old_node.live_shared_function_infos) {
old_node.live_shared_function_infos.forEach(function (old_info) {
%LiveEditReplaceFunctionCode(new_info.raw_array,
old_info.raw_array);
// The function got a new code. However, this new code brings all new
// instances of SharedFunctionInfo for nested functions. However,
// we want the original instances to be used wherever possible.
// (This is because old instances and new instances will be both
// linked to a script and breakpoints subsystem does not really
// expects this; neither does LiveEdit subsystem on next call).
for (var i = 0; i < old_node.children.length; i++) {
if (old_node.children[i].corresponding_node) {
var corresponding_child_info =
old_node.children[i].corresponding_node.info.
shared_function_info;
if (old_node.children[i].live_shared_function_infos) {
old_node.children[i].live_shared_function_infos.
forEach(function (old_child_info) {
%LiveEditReplaceRefToNestedFunction(
old_info.info,
corresponding_child_info,
old_child_info.info);
});
}
}
}
});
change_log.push( {function_patched: new_info.function_name} );
} else {
change_log.push( {function_patched: new_info.function_name,
function_info_not_found: true} );
}
}
// Makes a function associated with another instance of a script (the
// one representing its old version). This way the function still
// may access its own text.
function LinkToOldScript(old_info_node, old_script, report_array) {
if (old_info_node.live_shared_function_infos) {
old_info_node.live_shared_function_infos.
forEach(function (info) {
%LiveEditFunctionSetScript(info.info, old_script);
});
report_array.push( { name: old_info_node.info.function_name } );
} else {
report_array.push(
{ name: old_info_node.info.function_name, not_found: true } );
}
}
function Assert(condition, message) {
if (!condition) {
if (message) {
throw "Assert " + message;
} else {
throw "Assert";
}
}
}
function DiffChunk(pos1, pos2, len1, len2) {
this.pos1 = pos1;
this.pos2 = pos2;
this.len1 = len1;
this.len2 = len2;
}
function PosTranslator(diff_array) {
var chunks = new GlobalArray();
var current_diff = 0;
for (var i = 0; i < diff_array.length; i += 3) {
var pos1_begin = diff_array[i];
var pos2_begin = pos1_begin + current_diff;
var pos1_end = diff_array[i + 1];
var pos2_end = diff_array[i + 2];
chunks.push(new DiffChunk(pos1_begin, pos2_begin, pos1_end - pos1_begin,
pos2_end - pos2_begin));
current_diff = pos2_end - pos1_end;
}
this.chunks = chunks;
}
PosTranslator.prototype.GetChunks = function() {
return this.chunks;
};
PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
var array = this.chunks;
if (array.length == 0 || pos < array[0].pos1) {
return pos;
}
var chunk_index1 = 0;
var chunk_index2 = array.length - 1;
while (chunk_index1 < chunk_index2) {
var middle_index = MathFloor((chunk_index1 + chunk_index2) / 2);
if (pos < array[middle_index + 1].pos1) {
chunk_index2 = middle_index;
} else {
chunk_index1 = middle_index + 1;
}
}
var chunk = array[chunk_index1];
if (pos >= chunk.pos1 + chunk.len1) {
return pos + chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
}
if (!inside_chunk_handler) {
inside_chunk_handler = PosTranslator.DefaultInsideChunkHandler;
}
return inside_chunk_handler(pos, chunk);
};
PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) {
Assert(false, "Cannot translate position in changed area");
};
PosTranslator.ShiftWithTopInsideChunkHandler =
function(pos, diff_chunk) {
// We carelessly do not check whether we stay inside the chunk after
// translation.
return pos - diff_chunk.pos1 + diff_chunk.pos2;
};
var FunctionStatus = {
// No change to function or its inner functions; however its positions
// in script may have been shifted.
UNCHANGED: "unchanged",
// The code of a function remains unchanged, but something happened inside
// some inner functions.
SOURCE_CHANGED: "source changed",
// The code of a function is changed or some nested function cannot be
// properly patched so this function must be recompiled.
CHANGED: "changed",
// Function is changed but cannot be patched.
DAMAGED: "damaged"
};
function CodeInfoTreeNode(code_info, children, array_index) {
this.info = code_info;
this.children = children;
// an index in array of compile_info
this.array_index = array_index;
this.parent = UNDEFINED;
this.status = FunctionStatus.UNCHANGED;
// Status explanation is used for debugging purposes and will be shown
// in user UI if some explanations are needed.
this.status_explanation = UNDEFINED;
this.new_start_pos = UNDEFINED;
this.new_end_pos = UNDEFINED;
this.corresponding_node = UNDEFINED;
this.unmatched_new_nodes = UNDEFINED;
// 'Textual' correspondence/matching is weaker than 'pure'
// correspondence/matching. We need 'textual' level for visual presentation
// in UI, we use 'pure' level for actual code manipulation.
// Sometimes only function body is changed (functions in old and new script
// textually correspond), but we cannot patch the code, so we see them
// as an old function deleted and new function created.
this.textual_corresponding_node = UNDEFINED;
this.textually_unmatched_new_nodes = UNDEFINED;
this.live_shared_function_infos = UNDEFINED;
}
// From array of function infos that is implicitly a tree creates
// an actual tree of functions in script.
function BuildCodeInfoTree(code_info_array) {
// Throughtout all function we iterate over input array.
var index = 0;
// Recursive function that builds a branch of tree.
function BuildNode() {
var my_index = index;
index++;
var child_array = new GlobalArray();
while (index < code_info_array.length &&
code_info_array[index].outer_index == my_index) {
child_array.push(BuildNode());
}
var node = new CodeInfoTreeNode(code_info_array[my_index], child_array,
my_index);
for (var i = 0; i < child_array.length; i++) {
child_array[i].parent = node;
}
return node;
}
var root = BuildNode();
Assert(index == code_info_array.length);
return root;
}
// Applies a list of the textual diff chunks onto the tree of functions.
// Determines status of each function (from unchanged to damaged). However
// children of unchanged functions are ignored.
function MarkChangedFunctions(code_info_tree, chunks) {
// A convenient iterator over diff chunks that also translates
// positions from old to new in a current non-changed part of script.
var chunk_it = new function() {
var chunk_index = 0;
var pos_diff = 0;
this.current = function() { return chunks[chunk_index]; };
this.next = function() {
var chunk = chunks[chunk_index];
pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
chunk_index++;
};
this.done = function() { return chunk_index >= chunks.length; };
this.TranslatePos = function(pos) { return pos + pos_diff; };
};
// A recursive function that processes internals of a function and all its
// inner functions. Iterator chunk_it initially points to a chunk that is
// below function start.
function ProcessInternals(info_node) {
info_node.new_start_pos = chunk_it.TranslatePos(
info_node.info.start_position);
var child_index = 0;
var code_changed = false;
var source_changed = false;
// Simultaneously iterates over child functions and over chunks.
while (!chunk_it.done() &&
chunk_it.current().pos1 < info_node.info.end_position) {
if (child_index < info_node.children.length) {
var child = info_node.children[child_index];
if (child.info.end_position <= chunk_it.current().pos1) {
ProcessUnchangedChild(child);
child_index++;
continue;
} else if (child.info.start_position >=
chunk_it.current().pos1 + chunk_it.current().len1) {
code_changed = true;
chunk_it.next();
continue;
} else if (child.info.start_position <= chunk_it.current().pos1 &&
child.info.end_position >= chunk_it.current().pos1 +
chunk_it.current().len1) {
ProcessInternals(child);
source_changed = source_changed ||
( child.status != FunctionStatus.UNCHANGED );
code_changed = code_changed ||
( child.status == FunctionStatus.DAMAGED );
child_index++;
continue;
} else {
code_changed = true;
child.status = FunctionStatus.DAMAGED;
child.status_explanation =
"Text diff overlaps with function boundary";
child_index++;
continue;
}
} else {
if (chunk_it.current().pos1 + chunk_it.current().len1 <=
info_node.info.end_position) {
info_node.status = FunctionStatus.CHANGED;
chunk_it.next();
continue;
} else {
info_node.status = FunctionStatus.DAMAGED;
info_node.status_explanation =
"Text diff overlaps with function boundary";
return;
}
}
Assert("Unreachable", false);
}
while (child_index < info_node.children.length) {
var child = info_node.children[child_index];
ProcessUnchangedChild(child);
child_index++;
}
if (code_changed) {
info_node.status = FunctionStatus.CHANGED;
} else if (source_changed) {
info_node.status = FunctionStatus.SOURCE_CHANGED;
}
info_node.new_end_pos =
chunk_it.TranslatePos(info_node.info.end_position);
}
function ProcessUnchangedChild(node) {
node.new_start_pos = chunk_it.TranslatePos(node.info.start_position);
node.new_end_pos = chunk_it.TranslatePos(node.info.end_position);
}
ProcessInternals(code_info_tree);
}
// For each old function (if it is not damaged) tries to find a corresponding
// function in new script. Typically it should succeed (non-damaged functions
// by definition may only have changes inside their bodies). However there are
// reasons for correspondence not to be found; function with unmodified text
// in new script may become enclosed into other function; the innocent change
// inside function body may in fact be something like "} function B() {" that
// splits a function into 2 functions.
function FindCorrespondingFunctions(old_code_tree, new_code_tree) {
// A recursive function that tries to find a correspondence for all
// child functions and for their inner functions.
function ProcessNode(old_node, new_node) {
var scope_change_description =
IsFunctionContextLocalsChanged(old_node.info, new_node.info);
if (scope_change_description) {
old_node.status = FunctionStatus.CHANGED;
}
var old_children = old_node.children;
var new_children = new_node.children;
var unmatched_new_nodes_list = [];
var textually_unmatched_new_nodes_list = [];
var old_index = 0;
var new_index = 0;
while (old_index < old_children.length) {
if (old_children[old_index].status == FunctionStatus.DAMAGED) {
old_index++;
} else if (new_index < new_children.length) {
if (new_children[new_index].info.start_position <
old_children[old_index].new_start_pos) {
unmatched_new_nodes_list.push(new_children[new_index]);
textually_unmatched_new_nodes_list.push(new_children[new_index]);
new_index++;
} else if (new_children[new_index].info.start_position ==
old_children[old_index].new_start_pos) {
if (new_children[new_index].info.end_position ==
old_children[old_index].new_end_pos) {
old_children[old_index].corresponding_node =
new_children[new_index];
old_children[old_index].textual_corresponding_node =
new_children[new_index];
if (scope_change_description) {
old_children[old_index].status = FunctionStatus.DAMAGED;
old_children[old_index].status_explanation =
"Enclosing function is now incompatible. " +
scope_change_description;
old_children[old_index].corresponding_node = UNDEFINED;
} else if (old_children[old_index].status !=
FunctionStatus.UNCHANGED) {
ProcessNode(old_children[old_index],
new_children[new_index]);
if (old_children[old_index].status == FunctionStatus.DAMAGED) {
unmatched_new_nodes_list.push(
old_children[old_index].corresponding_node);
old_children[old_index].corresponding_node = UNDEFINED;
old_node.status = FunctionStatus.CHANGED;
}
} else {
ProcessNode(old_children[old_index], new_children[new_index]);
}
} else {
old_children[old_index].status = FunctionStatus.DAMAGED;
old_children[old_index].status_explanation =
"No corresponding function in new script found";
old_node.status = FunctionStatus.CHANGED;
unmatched_new_nodes_list.push(new_children[new_index]);
textually_unmatched_new_nodes_list.push(new_children[new_index]);
}
new_index++;
old_index++;
} else {
old_children[old_index].status = FunctionStatus.DAMAGED;
old_children[old_index].status_explanation =
"No corresponding function in new script found";
old_node.status = FunctionStatus.CHANGED;
old_index++;
}
} else {
old_children[old_index].status = FunctionStatus.DAMAGED;
old_children[old_index].status_explanation =
"No corresponding function in new script found";
old_node.status = FunctionStatus.CHANGED;
old_index++;
}
}
while (new_index < new_children.length) {
unmatched_new_nodes_list.push(new_children[new_index]);
textually_unmatched_new_nodes_list.push(new_children[new_index]);
new_index++;
}
if (old_node.status == FunctionStatus.CHANGED) {
if (old_node.info.param_num != new_node.info.param_num) {
old_node.status = FunctionStatus.DAMAGED;
old_node.status_explanation = "Changed parameter number: " +
old_node.info.param_num + " and " + new_node.info.param_num;
}
}
old_node.unmatched_new_nodes = unmatched_new_nodes_list;
old_node.textually_unmatched_new_nodes =
textually_unmatched_new_nodes_list;
}
ProcessNode(old_code_tree, new_code_tree);
old_code_tree.corresponding_node = new_code_tree;
old_code_tree.textual_corresponding_node = new_code_tree;
Assert(old_code_tree.status != FunctionStatus.DAMAGED,
"Script became damaged");
}
function FindLiveSharedInfos(old_code_tree, script) {
var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
var shared_infos = new GlobalArray();
for (var i = 0; i < shared_raw_list.length; i++) {
shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
}
// Finds all SharedFunctionInfos that corresponds to compile info
// in old version of the script.
function FindFunctionInfos(compile_info) {
var wrappers = [];
for (var i = 0; i < shared_infos.length; i++) {
var wrapper = shared_infos[i];
if (wrapper.start_position == compile_info.start_position &&
wrapper.end_position == compile_info.end_position) {
wrappers.push(wrapper);
}
}
if (wrappers.length > 0) {
return wrappers;
}
}
function TraverseTree(node) {
node.live_shared_function_infos = FindFunctionInfos(node.info);
for (var i = 0; i < node.children.length; i++) {
TraverseTree(node.children[i]);
}
}
TraverseTree(old_code_tree);
}
// An object describing function compilation details. Its index fields
// apply to indexes inside array that stores these objects.
function FunctionCompileInfo(raw_array) {
this.function_name = raw_array[0];
this.start_position = raw_array[1];
this.end_position = raw_array[2];
this.param_num = raw_array[3];
this.scope_info = raw_array[4];
this.outer_index = raw_array[5];
this.shared_function_info = raw_array[6];
this.function_literal_id = raw_array[7];
this.next_sibling_index = null;
this.raw_array = raw_array;
}
function SharedInfoWrapper(raw_array) {
this.function_name = raw_array[0];
this.start_position = raw_array[1];
this.end_position = raw_array[2];
this.info = raw_array[3];
this.raw_array = raw_array;
}
// Changes positions (including all statements) in function.
function PatchPositions(old_info_node, diff_array, report_array) {
if (old_info_node.live_shared_function_infos) {
old_info_node.live_shared_function_infos.forEach(function (info) {
%LiveEditPatchFunctionPositions(info.raw_array,
diff_array);
});
report_array.push( { name: old_info_node.info.function_name } );
} else {
// TODO(LiveEdit): function is not compiled yet or is already collected.
report_array.push(
{ name: old_info_node.info.function_name, info_not_found: true } );
}
}
// Adds a suffix to script name to mark that it is old version.
function CreateNameForOldScript(script) {
// TODO(635): try better than this; support several changes.
return script.name + " (old)";
}
// Compares a function scope heap structure, old and new version, whether it
// changed or not. Returns explanation if they differ.
function IsFunctionContextLocalsChanged(function_info1, function_info2) {
var scope_info1 = function_info1.scope_info;
var scope_info2 = function_info2.scope_info;
var scope_info1_text;
var scope_info2_text;
if (scope_info1) {
scope_info1_text = scope_info1.toString();
} else {
scope_info1_text = "";
}
if (scope_info2) {
scope_info2_text = scope_info2.toString();
} else {
scope_info2_text = "";
}
if (scope_info1_text != scope_info2_text) {
return "Variable map changed: [" + scope_info1_text +
"] => [" + scope_info2_text + "]";
}
// No differences. Return undefined.
return;
}
// Minifier forward declaration.
var FunctionPatchabilityStatus;
// For array of wrapped shared function infos checks that none of them
// have activations on stack (of any thread). Throws a Failure exception
// if this proves to be false.
function CheckStackActivations(old_shared_wrapper_list,
new_shared_list,
change_log) {
var old_shared_list = new GlobalArray();
for (var i = 0; i < old_shared_wrapper_list.length; i++) {
old_shared_list[i] = old_shared_wrapper_list[i].info;
}
var result = %LiveEditCheckAndDropActivations(
old_shared_list, new_shared_list, true);
if (result[old_shared_wrapper_list.length]) {
// Extra array element may contain error message.
throw new Failure(result[old_shared_wrapper_list.length]);
}
var problems = new GlobalArray();
var dropped = new GlobalArray();
for (var i = 0; i < old_shared_list.length; i++) {
var shared = old_shared_wrapper_list[i];
if (result[i] == FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
dropped.push({ name: shared.function_name } );
} else if (result[i] != FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
var description = {
name: shared.function_name,
start_pos: shared.start_position,
end_pos: shared.end_position,
replace_problem:
FunctionPatchabilityStatus.SymbolName(result[i])
};
problems.push(description);
}
}
if (dropped.length > 0) {
change_log.push({ dropped_from_stack: dropped });
}
if (problems.length > 0) {
change_log.push( { functions_on_stack: problems } );
throw new Failure("Blocked by functions on stack");
}
return dropped.length;
}
// A copy of the FunctionPatchabilityStatus enum from liveedit.h
var FunctionPatchabilityStatus = {
AVAILABLE_FOR_PATCH: 1,
BLOCKED_ON_ACTIVE_STACK: 2,
BLOCKED_ON_OTHER_STACK: 3,
BLOCKED_UNDER_NATIVE_CODE: 4,
REPLACED_ON_ACTIVE_STACK: 5,
BLOCKED_UNDER_GENERATOR: 6,
BLOCKED_ACTIVE_GENERATOR: 7,
BLOCKED_NO_NEW_TARGET_ON_RESTART: 8
};
FunctionPatchabilityStatus.SymbolName = function(code) {
var enumeration = FunctionPatchabilityStatus;
for (var name in enumeration) {
if (enumeration[name] == code) {
return name;
}
}
};
// A logical failure in liveedit process. This means that change_log
// is valid and consistent description of what happened.
function Failure(message) {
this.message = message;
}
Failure.prototype.toString = function() {
return "LiveEdit Failure: " + this.message;
};
function CopyErrorPositionToDetails(e, details) {
function createPositionStruct(script, position) {
if (position == -1) return;
var location = %ScriptPositionInfo(script, position, true);
if (location == null) return;
return {
line: location.line + 1,
column: location.column + 1,
position: position
};
}
if (!("scriptObject" in e) || !("startPosition" in e)) {
return;
}
var script = e.scriptObject;
var position_struct = {
start: createPositionStruct(script, e.startPosition),
end: createPositionStruct(script, e.endPosition)
};
details.position = position_struct;
}
// LiveEdit main entry point: changes a script text to a new string.
function SetScriptSource(script, new_source, preview_only, change_log) {
var old_source = script.source;
var diff = CompareStrings(old_source, new_source);
return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
change_log);
}
function CompareStrings(s1, s2) {
return %LiveEditCompareStrings(s1, s2);
}
// Applies the change to the script.
// The change is always a substring (change_pos, change_pos + change_len)
// being replaced with a completely different string new_str.
// This API is a legacy and is obsolete.
//
// @param {Script} script that is being changed
// @param {Array} change_log a list that collects engineer-readable
// description of what happened.
function ApplySingleChunkPatch(script, change_pos, change_len, new_str,
change_log) {
var old_source = script.source;
// Prepare new source string.
var new_source = old_source.substring(0, change_pos) +
new_str + old_source.substring(change_pos + change_len);
return ApplyPatchMultiChunk(script,
[ change_pos, change_pos + change_len, change_pos + new_str.length],
new_source, false, change_log);
}
// Creates JSON description for a change tree.
function DescribeChangeTree(old_code_tree) {
function ProcessOldNode(node) {
var child_infos = [];
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
if (child.status != FunctionStatus.UNCHANGED) {
child_infos.push(ProcessOldNode(child));
}
}
var new_child_infos = [];
if (node.textually_unmatched_new_nodes) {
for (var i = 0; i < node.textually_unmatched_new_nodes.length; i++) {
var child = node.textually_unmatched_new_nodes[i];
new_child_infos.push(ProcessNewNode(child));
}
}
var res = {
name: node.info.function_name,
positions: DescribePositions(node),
status: node.status,
children: child_infos,
new_children: new_child_infos
};
if (node.status_explanation) {
res.status_explanation = node.status_explanation;
}
if (node.textual_corresponding_node) {
res.new_positions = DescribePositions(node.textual_corresponding_node);
}
return res;
}
function ProcessNewNode(node) {
var child_infos = [];
// Do not list ancestors.
if (false) {
for (var i = 0; i < node.children.length; i++) {
child_infos.push(ProcessNewNode(node.children[i]));
}
}
var res = {
name: node.info.function_name,
positions: DescribePositions(node),
children: child_infos,
};
return res;
}
function DescribePositions(node) {
return {
start_position: node.info.start_position,
end_position: node.info.end_position
};
}
return ProcessOldNode(old_code_tree);
}
// -------------------------------------------------------------------
// Exports
var LiveEdit = {};
LiveEdit.SetScriptSource = SetScriptSource;
LiveEdit.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
LiveEdit.Failure = Failure;
LiveEdit.TestApi = {
PosTranslator: PosTranslator,
CompareStrings: CompareStrings,
ApplySingleChunkPatch: ApplySingleChunkPatch
};
// Functions needed by the debugger runtime.
utils.InstallConstants(utils, [
"SetScriptSource", LiveEdit.SetScriptSource,
]);
utils.InstallConstants(global, [
"Debug", {},
]);
global.Debug.LiveEdit = LiveEdit;
})
...@@ -2149,47 +2149,5 @@ InnerPointerToCodeCache::InnerPointerToCodeCacheEntry* ...@@ -2149,47 +2149,5 @@ InnerPointerToCodeCache::InnerPointerToCodeCacheEntry*
} }
return entry; return entry;
} }
// -------------------------------------------------------------------------
#define DEFINE_WRAPPER(type, field) \
class field##_Wrapper : public ZoneObject { \
public: /* NOLINT */ \
field##_Wrapper(const field& original) : frame_(original) { \
} \
field frame_; \
};
STACK_FRAME_TYPE_LIST(DEFINE_WRAPPER)
#undef DEFINE_WRAPPER
static StackFrame* AllocateFrameCopy(StackFrame* frame, Zone* zone) {
#define FRAME_TYPE_CASE(type, field) \
case StackFrame::type: { \
field##_Wrapper* wrapper = \
new(zone) field##_Wrapper(*(reinterpret_cast<field*>(frame))); \
return &wrapper->frame_; \
}
switch (frame->type()) {
STACK_FRAME_TYPE_LIST(FRAME_TYPE_CASE)
default: UNREACHABLE();
}
#undef FRAME_TYPE_CASE
return nullptr;
}
Vector<StackFrame*> CreateStackMap(Isolate* isolate, Zone* zone) {
ZoneVector<StackFrame*> frames(zone);
for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
StackFrame* frame = AllocateFrameCopy(it.frame(), zone);
frames.push_back(frame);
}
return Vector<StackFrame*>(frames.data(), frames.size());
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -1323,11 +1323,6 @@ class SafeStackFrameIterator: public StackFrameIteratorBase { ...@@ -1323,11 +1323,6 @@ class SafeStackFrameIterator: public StackFrameIteratorBase {
StackFrame::Type top_frame_type_; StackFrame::Type top_frame_type_;
ExternalCallbackScope* external_callback_scope_; ExternalCallbackScope* external_callback_scope_;
}; };
// Reads all frames on the current stack and copies them into the current
// zone memory.
Vector<StackFrame*> CreateStackMap(Isolate* isolate, Zone* zone);
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
......
...@@ -1567,6 +1567,33 @@ Handle<Script> Factory::NewScriptWithId(Handle<String> source, int script_id, ...@@ -1567,6 +1567,33 @@ Handle<Script> Factory::NewScriptWithId(Handle<String> source, int script_id,
return script; return script;
} }
Handle<Script> Factory::CloneScript(Handle<Script> script) {
Heap* heap = isolate()->heap();
int script_id = isolate()->heap()->NextScriptId();
Handle<Script> new_script =
Handle<Script>::cast(NewStruct(SCRIPT_TYPE, TENURED));
new_script->set_source(script->source());
new_script->set_name(script->name());
new_script->set_id(script_id);
new_script->set_line_offset(script->line_offset());
new_script->set_column_offset(script->column_offset());
new_script->set_context_data(script->context_data());
new_script->set_type(script->type());
new_script->set_wrapper(script->wrapper());
new_script->set_line_ends(heap->undefined_value());
new_script->set_eval_from_shared_or_wrapped_arguments(
script->eval_from_shared_or_wrapped_arguments());
new_script->set_shared_function_infos(*empty_weak_fixed_array(),
SKIP_WRITE_BARRIER);
new_script->set_eval_from_position(script->eval_from_position());
new_script->set_flags(script->flags());
new_script->set_host_defined_options(script->host_defined_options());
heap->set_script_list(
*FixedArrayOfWeakCells::Add(isolate(), script_list(), new_script));
LOG(isolate(), ScriptEvent(Logger::ScriptEventType::kCreate, script_id));
return new_script;
}
Handle<CallableTask> Factory::NewCallableTask(Handle<JSReceiver> callable, Handle<CallableTask> Factory::NewCallableTask(Handle<JSReceiver> callable,
Handle<Context> context) { Handle<Context> context) {
DCHECK(callable->IsCallable()); DCHECK(callable->IsCallable());
......
...@@ -408,6 +408,7 @@ class V8_EXPORT_PRIVATE Factory { ...@@ -408,6 +408,7 @@ class V8_EXPORT_PRIVATE Factory {
PretenureFlag tenure = TENURED); PretenureFlag tenure = TENURED);
Handle<Script> NewScriptWithId(Handle<String> source, int script_id, Handle<Script> NewScriptWithId(Handle<String> source, int script_id,
PretenureFlag tenure = TENURED); PretenureFlag tenure = TENURED);
Handle<Script> CloneScript(Handle<Script> script);
Handle<BreakPointInfo> NewBreakPointInfo(int source_position); Handle<BreakPointInfo> NewBreakPointInfo(int source_position);
Handle<BreakPoint> NewBreakPoint(int id, Handle<String> condition); Handle<BreakPoint> NewBreakPoint(int id, Handle<String> condition);
......
...@@ -114,37 +114,7 @@ class ActualScript : public V8DebuggerScript { ...@@ -114,37 +114,7 @@ class ActualScript : public V8DebuggerScript {
: V8DebuggerScript(isolate, String16::fromInteger(script->Id()), : V8DebuggerScript(isolate, String16::fromInteger(script->Id()),
GetNameOrSourceUrl(script)), GetNameOrSourceUrl(script)),
m_isLiveEdit(isLiveEdit) { m_isLiveEdit(isLiveEdit) {
v8::Local<v8::String> tmp; Initialize(script);
if (script->SourceURL().ToLocal(&tmp)) m_sourceURL = toProtocolString(tmp);
if (script->SourceMappingURL().ToLocal(&tmp))
m_sourceMappingURL = toProtocolString(tmp);
m_startLine = script->LineOffset();
m_startColumn = script->ColumnOffset();
std::vector<int> lineEnds = script->LineEnds();
CHECK(lineEnds.size());
int source_length = lineEnds[lineEnds.size() - 1];
if (lineEnds.size()) {
m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
if (lineEnds.size() > 1) {
m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
} else {
m_endColumn = source_length + m_startColumn;
}
} else {
m_endLine = m_startLine;
m_endColumn = m_startColumn;
}
USE(script->ContextId().To(&m_executionContextId));
if (script->Source().ToLocal(&tmp)) {
m_source = toProtocolString(tmp);
}
m_isModule = script->IsModule();
m_script.Reset(m_isolate, script);
m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
} }
bool isLiveEdit() const override { return m_isLiveEdit; } bool isLiveEdit() const override { return m_isLiveEdit; }
...@@ -175,8 +145,8 @@ class ActualScript : public V8DebuggerScript { ...@@ -175,8 +145,8 @@ class ActualScript : public V8DebuggerScript {
return; return;
} }
if (preview) return; if (preview) return;
m_source = newSource;
m_hash = String16(); m_hash = String16();
Initialize(scope.Escape(result->script));
} }
bool getPossibleBreakpoints( bool getPossibleBreakpoints(
...@@ -259,6 +229,40 @@ class ActualScript : public V8DebuggerScript { ...@@ -259,6 +229,40 @@ class ActualScript : public V8DebuggerScript {
return m_script.Get(m_isolate); return m_script.Get(m_isolate);
} }
void Initialize(v8::Local<v8::debug::Script> script) {
v8::Local<v8::String> tmp;
if (script->SourceURL().ToLocal(&tmp)) m_sourceURL = toProtocolString(tmp);
if (script->SourceMappingURL().ToLocal(&tmp))
m_sourceMappingURL = toProtocolString(tmp);
m_startLine = script->LineOffset();
m_startColumn = script->ColumnOffset();
std::vector<int> lineEnds = script->LineEnds();
CHECK(lineEnds.size());
int source_length = lineEnds[lineEnds.size() - 1];
if (lineEnds.size()) {
m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
if (lineEnds.size() > 1) {
m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
} else {
m_endColumn = source_length + m_startColumn;
}
} else {
m_endLine = m_startLine;
m_endColumn = m_startColumn;
}
USE(script->ContextId().To(&m_executionContextId));
if (script->Source().ToLocal(&tmp)) {
m_source = toProtocolString(tmp);
}
m_isModule = script->IsModule();
m_script.Reset(m_isolate, script);
m_script.AnnotateStrongRetainer(kGlobalDebuggerScriptHandleLabel);
}
String16 m_sourceMappingURL; String16 m_sourceMappingURL;
bool m_isLiveEdit = false; bool m_isLiveEdit = false;
bool m_isModule = false; bool m_isModule = false;
......
...@@ -899,7 +899,7 @@ RUNTIME_FUNCTION(Runtime_LiveEditPatchScript) { ...@@ -899,7 +899,7 @@ RUNTIME_FUNCTION(Runtime_LiveEditPatchScript) {
Handle<Script> script(Script::cast(script_function->shared()->script()), Handle<Script> script(Script::cast(script_function->shared()->script()),
isolate); isolate);
v8::debug::LiveEditResult result; v8::debug::LiveEditResult result;
LiveEdit::PatchScript(script, new_source, &result); LiveEdit::PatchScript(isolate, script, new_source, false, &result);
switch (result.status) { switch (result.status) {
case v8::debug::LiveEditResult::COMPILE_ERROR: case v8::debug::LiveEditResult::COMPILE_ERROR:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked( return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
......
...@@ -287,19 +287,6 @@ namespace internal { ...@@ -287,19 +287,6 @@ namespace internal {
F(CreateObjectLiteral, 4, 1) \ F(CreateObjectLiteral, 4, 1) \
F(CreateRegExpLiteral, 4, 1) F(CreateRegExpLiteral, 4, 1)
#define FOR_EACH_INTRINSIC_LIVEEDIT(F) \
F(LiveEditCheckAndDropActivations, 3, 1) \
F(LiveEditCompareStrings, 2, 1) \
F(LiveEditFindSharedFunctionInfosForScript, 1, 1) \
F(LiveEditFixupScript, 2, 1) \
F(LiveEditFunctionSetScript, 2, 1) \
F(LiveEditFunctionSourceUpdated, 2, 1) \
F(LiveEditGatherCompileInfo, 2, 1) \
F(LiveEditPatchFunctionPositions, 2, 1) \
F(LiveEditReplaceFunctionCode, 2, 1) \
F(LiveEditReplaceRefToNestedFunction, 3, 1) \
F(LiveEditReplaceScript, 3, 1)
#define FOR_EACH_INTRINSIC_MATHS(F) F(GenerateRandomNumbers, 0, 1) #define FOR_EACH_INTRINSIC_MATHS(F) F(GenerateRandomNumbers, 0, 1)
#define FOR_EACH_INTRINSIC_MODULE(F) \ #define FOR_EACH_INTRINSIC_MODULE(F) \
...@@ -624,7 +611,6 @@ namespace internal { ...@@ -624,7 +611,6 @@ namespace internal {
FOR_EACH_INTRINSIC_INTERPRETER(F) \ FOR_EACH_INTRINSIC_INTERPRETER(F) \
FOR_EACH_INTRINSIC_INTL(F) \ FOR_EACH_INTRINSIC_INTL(F) \
FOR_EACH_INTRINSIC_LITERALS(F) \ FOR_EACH_INTRINSIC_LITERALS(F) \
FOR_EACH_INTRINSIC_LIVEEDIT(F) \
FOR_EACH_INTRINSIC_MATHS(F) \ FOR_EACH_INTRINSIC_MATHS(F) \
FOR_EACH_INTRINSIC_MODULE(F) \ FOR_EACH_INTRINSIC_MODULE(F) \
FOR_EACH_INTRINSIC_NUMBERS(F) \ FOR_EACH_INTRINSIC_NUMBERS(F) \
......
...@@ -203,7 +203,7 @@ void PatchFunctions(v8::Local<v8::Context> context, const char* source_a, ...@@ -203,7 +203,7 @@ void PatchFunctions(v8::Local<v8::Context> context, const char* source_a,
v8::debug::LiveEditResult* result = nullptr) { v8::debug::LiveEditResult* result = nullptr) {
v8::Isolate* isolate = context->GetIsolate(); v8::Isolate* isolate = context->GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
v8::HandleScope scope(isolate); v8::EscapableHandleScope scope(isolate);
v8::Local<v8::Script> script_a = v8::Local<v8::Script> script_a =
v8::Script::Compile(context, v8_str(isolate, source_a)).ToLocalChecked(); v8::Script::Compile(context, v8_str(isolate, source_a)).ToLocalChecked();
script_a->Run(context).ToLocalChecked(); script_a->Run(context).ToLocalChecked();
...@@ -213,20 +213,24 @@ void PatchFunctions(v8::Local<v8::Context> context, const char* source_a, ...@@ -213,20 +213,24 @@ void PatchFunctions(v8::Local<v8::Context> context, const char* source_a,
if (result) { if (result) {
LiveEdit::PatchScript( LiveEdit::PatchScript(
i_script_a, i_isolate->factory()->NewStringFromAsciiChecked(source_b), i_isolate, i_script_a,
i_isolate->factory()->NewStringFromAsciiChecked(source_b), false,
result); result);
if (result->status == v8::debug::LiveEditResult::COMPILE_ERROR) {
result->message = scope.Escape(result->message);
}
} else { } else {
v8::debug::LiveEditResult result; v8::debug::LiveEditResult result;
LiveEdit::PatchScript( LiveEdit::PatchScript(
i_script_a, i_isolate->factory()->NewStringFromAsciiChecked(source_b), i_isolate, i_script_a,
i_isolate->factory()->NewStringFromAsciiChecked(source_b), false,
&result); &result);
CHECK_EQ(result.status, v8::debug::LiveEditResult::OK); CHECK_EQ(result.status, v8::debug::LiveEditResult::OK);
} }
} }
} // anonymous namespace } // anonymous namespace
// TODO(kozyatinskiy): enable it with new liveedit implementation. TEST(LiveEditPatchFunctions) {
DISABLED_TEST(LiveEditPatchFunctions) {
LocalContext env; LocalContext env;
v8::HandleScope scope(env->GetIsolate()); v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local(); v8::Local<v8::Context> context = env.local();
...@@ -367,7 +371,6 @@ DISABLED_TEST(LiveEditPatchFunctions) { ...@@ -367,7 +371,6 @@ DISABLED_TEST(LiveEditPatchFunctions) {
->ToInt32(env->GetIsolate()) ->ToInt32(env->GetIsolate())
->Value(), ->Value(),
20); 20);
// Change inner functions. // Change inner functions.
PatchFunctions( PatchFunctions(
context, context,
...@@ -417,10 +420,53 @@ DISABLED_TEST(LiveEditPatchFunctions) { ...@@ -417,10 +420,53 @@ DISABLED_TEST(LiveEditPatchFunctions) {
v8::String::Utf8Value new_result_utf8(env->GetIsolate(), result); v8::String::Utf8Value new_result_utf8(env->GetIsolate(), result);
CHECK_NOT_NULL(strstr(*new_result_utf8, "cb")); CHECK_NOT_NULL(strstr(*new_result_utf8, "cb"));
} }
// TODO(kozyatinskiy): should work when we remove (.
PatchFunctions(context, "f = () => 2", "f = a => a");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "f(3)")
->ToInt32(env->GetIsolate())
->Value(),
2);
// Replace function with not a function.
PatchFunctions(context, "f = () => 2", "f = a == 2");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "f(3)")
->ToInt32(env->GetIsolate())
->Value(),
2);
// TODO(kozyatinskiy): should work when we put function into (...).
PatchFunctions(context, "f = a => 2", "f = (a => 5)()");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "f()")
->ToInt32(env->GetIsolate())
->Value(),
2);
PatchFunctions(context,
"f2 = null;\n"
"f = () => {\n"
" f2 = () => 5;\n"
" return f2();\n"
"}\n"
"f()\n",
"f2 = null;\n"
"f = () => {\n"
" for (var a = (() => 7)(), b = 0; a < 10; ++a,++b);\n"
" return b;\n"
"}\n"
"f()\n");
// TODO(kozyatinskiy): ditto.
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "f2()")
->ToInt32(env->GetIsolate())
->Value(),
5);
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "f()")
->ToInt32(env->GetIsolate())
->Value(),
3);
} }
// TODO(kozyatinskiy): enable it with new liveedit implementation. TEST(LiveEditCompileError) {
DISABLED_TEST(LiveEditCompileError) {
LocalContext env; LocalContext env;
v8::HandleScope scope(env->GetIsolate()); v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local(); v8::Local<v8::Context> context = env.local();
...@@ -452,10 +498,8 @@ DISABLED_TEST(LiveEditCompileError) { ...@@ -452,10 +498,8 @@ DISABLED_TEST(LiveEditCompileError) {
PatchFunctions(context, "function foo() {}", PatchFunctions(context, "function foo() {}",
"function foo() { return a # b; }", &result); "function foo() { return a # b; }", &result);
CHECK_EQ(result.status, debug::LiveEditResult::COMPILE_ERROR); CHECK_EQ(result.status, debug::LiveEditResult::COMPILE_ERROR);
// TODO(kozyatinskiy): should be 1. CHECK_EQ(result.line_number, 1);
CHECK_EQ(result.line_number, kNoSourcePosition); CHECK_EQ(result.column_number, 26);
// TODO(kozyatinskiy): should be 26.
CHECK_EQ(result.column_number, kNoSourcePosition);
} }
TEST(LiveEditFunctionExpression) { TEST(LiveEditFunctionExpression) {
...@@ -482,7 +526,8 @@ TEST(LiveEditFunctionExpression) { ...@@ -482,7 +526,8 @@ TEST(LiveEditFunctionExpression) {
i_isolate); i_isolate);
debug::LiveEditResult result; debug::LiveEditResult result;
LiveEdit::PatchScript( LiveEdit::PatchScript(
i_script, i_isolate->factory()->NewStringFromAsciiChecked(updated_source), i_isolate, i_script,
i_isolate->factory()->NewStringFromAsciiChecked(updated_source), false,
&result); &result);
CHECK_EQ(result.status, debug::LiveEditResult::OK); CHECK_EQ(result.status, debug::LiveEditResult::OK);
{ {
......
// Copyright 2018 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.
// Flags: --allow-natives-syntax
Debug = debug.Debug;
function test(i) {
if (i == 3) {
return (function* gen() { yield test(i - 1); })().next().value;
} else if (i > 1) {
return test(i - 1);
} else {
debugger;
return 5;
}
}
let patch = null, exception = null;
Debug.setListener(listener);
patch = ['return 5', 'return 3'];
assertEquals(3, test(2)); // no running generator
patch = ['return 3', 'return -1'];
assertEquals(3, test(3)); // there is running generator
assertEquals(exception,
'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
Debug.setListener(null);
function listener(event) {
if (event != Debug.DebugEvent.Break || !patch) return;
try {
%LiveEditPatchScript(test,
Debug.scriptSource(test).replace(patch[0], patch[1]));
} catch (e) {
exception = e;
}
patch = null;
}
...@@ -53,7 +53,6 @@ Debug.setListener(null); ...@@ -53,7 +53,6 @@ Debug.setListener(null);
assertNull(exception); assertNull(exception);
assertEquals(["Emacs", "Eclipse", "Vim"], results); assertEquals(["Emacs", "Eclipse", "Vim"], results);
// TODO(kozyatinskiy): uncomment lines with new liveedit implementation.
assertEquals([ assertEquals([
"debugger;", "debugger;",
"results.push(BestEditor());", "results.push(BestEditor());",
...@@ -62,12 +61,12 @@ assertEquals([ ...@@ -62,12 +61,12 @@ assertEquals([
"results.push(BestEditor());", "results.push(BestEditor());",
"results.push(BestEditor());", "results.push(BestEditor());",
" return 'Emacs';", " return 'Emacs';",
// " return 'Eclipse';", " return 'Eclipse';",
" return 'Eclipse';", " return 'Eclipse';",
"results.push(BestEditor());", "results.push(BestEditor());",
"results.push(BestEditor());", "results.push(BestEditor());",
" return 'Eclipse';", " return 'Eclipse';",
// " return 'Vim';", " return 'Vim';",
" return 'Vim';", " return 'Vim';",
"results.push(BestEditor());", "results.push(BestEditor());",
"Debug.setListener(null);" "Debug.setListener(null);"
......
...@@ -46,8 +46,7 @@ function Replace(fun, original, patch) { ...@@ -46,8 +46,7 @@ function Replace(fun, original, patch) {
try { try {
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch)); %LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
} catch (e) { } catch (e) {
// TODO(kozyatinskiy): message should be BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME. assertEquals(e, 'LiveEdit failed: BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME');
assertEquals(e, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
exceptions++; exceptions++;
} }
}); });
......
...@@ -43,8 +43,7 @@ function Replace(fun, original, patch) { ...@@ -43,8 +43,7 @@ function Replace(fun, original, patch) {
try { try {
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch)); %LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
} catch (e) { } catch (e) {
// TODO(kozyatinskiy): message should be BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME. assertEquals(e, 'LiveEdit failed: BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME');
assertEquals(e, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
exceptions++; exceptions++;
} }
}); });
......
...@@ -97,10 +97,9 @@ function patch(fun, from, to) { ...@@ -97,10 +97,9 @@ function patch(fun, from, to) {
// Patching will fail however when a live iterator is suspended. // Patching will fail however when a live iterator is suspended.
iter = generator(function(){}); iter = generator(function(){});
assertIteratorResult(undefined, false, iter.next()); assertIteratorResult(undefined, false, iter.next());
// TODO(kozyatinskiy): message should be BLOCKED_BY_RUNNING_GENERATOR.
assertThrowsEquals(function() { assertThrowsEquals(function() {
patch(generator, '\'Capybara\'', '\'Tapir\'') patch(generator, '\'Capybara\'', '\'Tapir\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME'); }, 'LiveEdit failed: BLOCKED_BY_RUNNING_GENERATOR');
assertIteratorResult("Capybara", true, iter.next()); assertIteratorResult("Capybara", true, iter.next());
// Try to patch functions with activations inside and outside generator // Try to patch functions with activations inside and outside generator
......
...@@ -106,10 +106,9 @@ function patch(fun, from, to) { ...@@ -106,10 +106,9 @@ function patch(fun, from, to) {
// Patching will fail however when an async function is suspended. // Patching will fail however when an async function is suspended.
var resolve; var resolve;
promise = asyncfn(function(){return new Promise(function(r){resolve = r})}); promise = asyncfn(function(){return new Promise(function(r){resolve = r})});
// TODO(kozyatinskiy): message should be BLOCKED_BY_RUNNING_GENERATOR.
assertThrowsEquals(function() { assertThrowsEquals(function() {
patch(asyncfn, '\'Capybara\'', '\'Tapir\'') patch(asyncfn, '\'Capybara\'', '\'Tapir\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME'); }, 'LiveEdit failed: BLOCKED_BY_RUNNING_GENERATOR');
resolve(); resolve();
assertPromiseValue("Capybara", promise); assertPromiseValue("Capybara", promise);
......
Breakpoint in liveedited script
Update script source
{
callFrames : [
]
stackChanged : false
}
Set breakpoint
{
actualLocation : {
columnNumber : 0
lineNumber : 3
scriptId : <scriptId>
}
breakpointId : <breakpointId>
}
Call function foo and dump stack
foo (:3:0)
(anonymous) (:0:0)
// Copyright 2018 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.
let {session, contextGroup, Protocol} =
InspectorTest.start('Breakpoint in liveedited script');
contextGroup.addScript(
`function foo() {
}
var f = foo;`);
const newSource = `function boo() {
}
function foo() {
}
f = foo;`;
(async function test() {
session.setupScriptMap();
Protocol.Debugger.enable();
const {params: {scriptId}} = await Protocol.Debugger.onceScriptParsed();
InspectorTest.log('Update script source');
let {result} = await Protocol.Debugger.setScriptSource(
{scriptId, scriptSource: newSource})
InspectorTest.logMessage(result);
InspectorTest.log('Set breakpoint');
({result} = await Protocol.Debugger.setBreakpoint({location:{
scriptId,
lineNumber: 3,
columnNumber: 0
}}));
InspectorTest.logMessage(result);
InspectorTest.log('Call function foo and dump stack');
Protocol.Runtime.evaluate({expression: 'foo()'});
const {params:{callFrames}} = await Protocol.Debugger.oncePaused();
session.logCallFrames(callFrames);
InspectorTest.completeTest();
})();
...@@ -14,10 +14,10 @@ Running test: testSourceWithSyntaxError ...@@ -14,10 +14,10 @@ Running test: testSourceWithSyntaxError
id : <messageId> id : <messageId>
result : { result : {
exceptionDetails : { exceptionDetails : {
columnNumber : 0 columnNumber : 11
exceptionId : <exceptionId> exceptionId : <exceptionId>
lineNumber : 0 lineNumber : 1
text : Invalid or unexpected token text : Uncaught SyntaxError: Invalid or unexpected token
} }
} }
} }
...@@ -10,10 +10,10 @@ TestExpression(2,4) === 8 ...@@ -10,10 +10,10 @@ TestExpression(2,4) === 8
Update current script source 'a * b' -> 'a # b'.. Update current script source 'a * b' -> 'a # b'..
{ {
exceptionDetails : { exceptionDetails : {
columnNumber : 0 columnNumber : 13
exceptionId : <exceptionId> exceptionId : <exceptionId>
lineNumber : 0 lineNumber : 1
text : Invalid or unexpected token text : Uncaught SyntaxError: Invalid or unexpected token
} }
} }
TestExpression(2,4) === 8 TestExpression(2,4) === 8
...@@ -297,7 +297,7 @@ class SourceProcessor(SourceFileProcessor): ...@@ -297,7 +297,7 @@ class SourceProcessor(SourceFileProcessor):
m = pattern.match(line) m = pattern.match(line)
if m: if m:
runtime_functions.append(m.group(1)) runtime_functions.append(m.group(1))
if len(runtime_functions) < 475: if len(runtime_functions) < 450:
print ("Runtime functions list is suspiciously short. " print ("Runtime functions list is suspiciously short. "
"Consider updating the presubmit script.") "Consider updating the presubmit script.")
sys.exit(1) sys.exit(1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment