[debug] migrate all liveedit tests to use LiveEdit::PatchScript

After this CL all liveedit tests call the same LiveEdit::PatchScript
method. This method will be updated later.
As well some new liveedit cctests added, unfortunately part of them
do not work with current implementation.

R=dgozman@chromium.org,yangguo@chromium.org

Bug: v8:7862
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: I3521af12b0f95b39d13aaafb1d1cf60f3f642a97
Reviewed-on: https://chromium-review.googlesource.com/1108382
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53936}
This commit is contained in:
Alexey Kozyatinskiy 2018-06-21 07:24:46 -07:00 committed by Commit Bot
parent 1cda2a6c29
commit 69d166fcef
36 changed files with 557 additions and 301 deletions

View File

@ -33,6 +33,7 @@
#include "src/debug/debug-evaluate.h"
#include "src/debug/debug-type-profile.h"
#include "src/debug/debug.h"
#include "src/debug/liveedit.h"
#include "src/deoptimizer.h"
#include "src/detachable-vector.h"
#include "src/execution.h"
@ -9404,11 +9405,12 @@ v8::debug::Location debug::Script::GetSourceLocation(int offset) const {
}
bool debug::Script::SetScriptSource(v8::Local<v8::String> newSource,
bool preview, bool* stack_changed) const {
bool preview,
debug::LiveEditResult* result) const {
i::Handle<i::Script> script = Utils::OpenHandle(this);
i::Isolate* isolate = script->GetIsolate();
return isolate->debug()->SetScriptSource(
script, Utils::OpenHandle(*newSource), preview, stack_changed);
script, Utils::OpenHandle(*newSource), preview, result);
}
bool debug::Script::SetBreakpoint(v8::Local<v8::String> condition,

View File

@ -86,6 +86,25 @@ void BreakRightNow(Isolate* isolate);
bool AllFramesOnStackAreBlackboxed(Isolate* isolate);
struct LiveEditResult {
enum Status {
OK,
COMPILE_ERROR,
BLOCKED_BY_RUNNING_GENERATOR,
BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME,
BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME,
BLOCKED_BY_ACTIVE_FUNCTION,
BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME,
FRAME_RESTART_IS_NOT_SUPPORTED
};
Status status = OK;
bool stack_changed = false;
// Fields below are available only for COMPILE_ERROR.
v8::Local<v8::String> message;
int line_number = -1;
int column_number = -1;
};
/**
* Native wrapper around v8::internal::Script object.
*/
@ -114,7 +133,7 @@ class V8_EXPORT_PRIVATE Script {
int GetSourceOffset(const debug::Location& location) const;
v8::debug::Location GetSourceLocation(int offset) const;
bool SetScriptSource(v8::Local<v8::String> newSource, bool preview,
bool* stack_changed) const;
LiveEditResult* result) const;
bool SetBreakpoint(v8::Local<v8::String> condition, debug::Location* location,
BreakpointId* id) const;
};

View File

@ -601,7 +601,7 @@ bool Debug::IsMutedAtCurrentLocation(JavaScriptFrame* frame) {
MaybeHandle<Object> Debug::CallFunction(const char* name, int argc,
Handle<Object> args[],
bool catch_exceptions) {
MaybeHandle<Object>* maybe_exception) {
AllowJavascriptExecutionDebugOnly allow_script(isolate_);
PostponeInterruptsScope no_interrupts(isolate_);
AssertDebugContext();
@ -610,17 +610,11 @@ MaybeHandle<Object> Debug::CallFunction(const char* name, int argc,
Handle<JSFunction> fun = Handle<JSFunction>::cast(
JSReceiver::GetProperty(isolate_, holder, name).ToHandleChecked());
Handle<Object> undefined = isolate_->factory()->undefined_value();
if (catch_exceptions) {
MaybeHandle<Object> maybe_exception;
return Execution::TryCall(isolate_, fun, undefined, argc, args,
Execution::MessageHandling::kReport,
&maybe_exception);
} else {
return Execution::Call(isolate_, fun, undefined, argc, args);
}
maybe_exception);
}
// Check whether a single break point object is triggered.
bool Debug::CheckBreakPoint(Handle<BreakPoint> break_point,
bool is_break_at_entry) {
@ -1907,22 +1901,52 @@ bool Debug::CanBreakAtEntry(Handle<SharedFunctionInfo> shared) {
}
bool Debug::SetScriptSource(Handle<Script> script, Handle<String> source,
bool preview, bool* stack_changed) {
bool preview, debug::LiveEditResult* output) {
SaveContext save(isolate_);
StackFrame::Id frame_id = break_frame_id();
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;
}
set_live_edit_enabled(true);
Handle<Object> script_wrapper = Script::GetWrapper(script);
Handle<Object> argv[] = {script_wrapper, source,
isolate_->factory()->ToBoolean(preview),
isolate_->factory()->NewJSArray(0)};
Handle<Object> result;
if (!CallFunction("SetScriptSource", arraysize(argv), argv, false)
MaybeHandle<Object> maybe_exception;
if (!CallFunction("SetScriptSource", arraysize(argv), argv, &maybe_exception)
.ToHandle(&result)) {
isolate_->OptionalRescheduleException(false);
Handle<Object> pending_exception = maybe_exception.ToHandleChecked();
set_live_edit_enabled(false);
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);
}
}
return false;
}
set_live_edit_enabled(false);
@ -1930,7 +1954,8 @@ bool Debug::SetScriptSource(Handle<Script> script, Handle<String> source,
JSReceiver::GetProperty(isolate_, Handle<JSObject>::cast(result),
"stack_modified")
.ToHandleChecked();
*stack_changed = stack_changed_value->IsTrue(isolate_);
output->stack_changed = stack_changed_value->IsTrue(isolate_);
output->status = debug::LiveEditResult::OK;
return true;
}

View File

@ -313,7 +313,7 @@ class Debug {
// change. stack_changed is true if after editing script on pause stack is
// changed and client should request stack trace again.
bool SetScriptSource(Handle<Script> script, Handle<String> source,
bool preview, bool* stack_changed);
bool preview, debug::LiveEditResult* result);
// Threading support.
char* ArchiveDebug(char* to);
@ -467,7 +467,7 @@ class Debug {
bool CheckBreakPoint(Handle<BreakPoint> break_point, bool is_break_at_entry);
MaybeHandle<Object> CallFunction(const char* name, int argc,
Handle<Object> args[],
bool catch_exceptions = true);
MaybeHandle<Object>* maybe_exception);
inline void AssertDebugContext() {
DCHECK(in_debug_scope());

View File

@ -1597,5 +1597,10 @@ int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& changes,
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 v8

View File

@ -29,6 +29,9 @@
#include "src/ast/ast-traversal-visitor.h"
namespace v8 {
namespace debug {
struct LiveEditResult;
}
namespace internal {
class JavaScriptFrame;
@ -140,6 +143,8 @@ class LiveEdit : AllStatic {
std::vector<SourceChangeRange>* changes);
static int TranslatePosition(const std::vector<SourceChangeRange>& changed,
int position);
static void PatchScript(Handle<Script> script, Handle<String> source,
debug::LiveEditResult* result);
// A copy of this is in liveedit.js.
enum FunctionPatchabilityStatus {

View File

@ -294,62 +294,6 @@ Response buildScopes(v8::debug::ScopeIterator* iterator,
return Response::OK();
}
bool liveEditExceptionToDetails(
V8InspectorImpl* inspector, v8::Local<v8::Context> context,
v8::Local<v8::Value> exceptionValue,
Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
if (!exceptionValue->IsObject()) return false;
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::Object> exception = exceptionValue.As<v8::Object>();
v8::Local<v8::Value> detailsValue;
if (!exception->Get(context, toV8String(isolate, "details"))
.ToLocal(&detailsValue) ||
!detailsValue->IsObject()) {
return false;
}
v8::Local<v8::Object> details = detailsValue.As<v8::Object>();
v8::Local<v8::Value> message;
if (!details->Get(context, toV8String(isolate, "syntaxErrorMessage"))
.ToLocal(&message) ||
!message->IsString()) {
return false;
}
v8::Local<v8::Value> positionValue;
if (!details->Get(context, toV8String(isolate, "position"))
.ToLocal(&positionValue) ||
!positionValue->IsObject()) {
return false;
}
v8::Local<v8::Value> startPositionValue;
if (!positionValue.As<v8::Object>()
->Get(context, toV8String(isolate, "start"))
.ToLocal(&startPositionValue) ||
!startPositionValue->IsObject()) {
return false;
}
v8::Local<v8::Object> startPosition = startPositionValue.As<v8::Object>();
v8::Local<v8::Value> lineValue;
if (!startPosition->Get(context, toV8String(isolate, "line"))
.ToLocal(&lineValue) ||
!lineValue->IsInt32()) {
return false;
}
v8::Local<v8::Value> columnValue;
if (!startPosition->Get(context, toV8String(isolate, "column"))
.ToLocal(&columnValue) ||
!columnValue->IsInt32()) {
return false;
}
*exceptionDetails =
protocol::Runtime::ExceptionDetails::create()
.setExceptionId(inspector->nextExceptionId())
.setText(toProtocolString(message.As<v8::String>()))
.setLineNumber(lineValue->Int32Value(context).FromJust() - 1)
.setColumnNumber(columnValue->Int32Value(context).FromJust() - 1)
.build();
return true;
}
protocol::DictionaryValue* getOrCreateObject(protocol::DictionaryValue* object,
const String16& key) {
protocol::DictionaryValue* value = object->getObject(key);
@ -927,23 +871,22 @@ Response V8DebuggerAgentImpl::setScriptSource(
v8::HandleScope handleScope(m_isolate);
v8::Local<v8::Context> context = inspected->context();
v8::Context::Scope contextScope(context);
v8::TryCatch tryCatch(m_isolate);
bool stackChangedValue = false;
it->second->setSource(newContent, dryRun.fromMaybe(false),
&stackChangedValue);
if (tryCatch.HasCaught()) {
if (liveEditExceptionToDetails(m_inspector, context, tryCatch.Exception(),
optOutCompileError)) {
v8::debug::LiveEditResult result;
it->second->setSource(newContent, dryRun.fromMaybe(false), &result);
if (result.status != v8::debug::LiveEditResult::OK) {
*optOutCompileError =
protocol::Runtime::ExceptionDetails::create()
.setExceptionId(m_inspector->nextExceptionId())
.setText(toProtocolString(result.message))
.setLineNumber(result.line_number != -1 ? result.line_number - 1
: 0)
.setColumnNumber(result.column_number != -1 ? result.column_number
: 0)
.build();
return Response::OK();
}
v8::Local<v8::Message> message = tryCatch.Message();
if (!message.IsEmpty())
return Response::Error(toProtocolStringWithTypeCheck(message->Get()));
else
return Response::InternalError();
} else {
*stackChanged = stackChangedValue;
*stackChanged = result.stack_changed;
}
std::unique_ptr<Array<CallFrame>> callFrames;
Response response = currentCallFrames(&callFrames);

View File

@ -166,12 +166,12 @@ class ActualScript : public V8DebuggerScript {
}
void setSource(const String16& newSource, bool preview,
bool* stackChanged) override {
v8::debug::LiveEditResult* result) override {
DCHECK(!isModule());
v8::HandleScope scope(m_isolate);
v8::EscapableHandleScope scope(m_isolate);
v8::Local<v8::String> v8Source = toV8String(m_isolate, newSource);
if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview,
stackChanged)) {
if (!m_script.Get(m_isolate)->SetScriptSource(v8Source, preview, result)) {
result->message = scope.Escape(result->message);
return;
}
if (preview) return;
@ -290,7 +290,9 @@ class WasmVirtualScript : public V8DebuggerScript {
bool isLiveEdit() const override { return false; }
bool isModule() const override { return false; }
void setSourceMappingURL(const String16&) override {}
void setSource(const String16&, bool, bool*) override { UNREACHABLE(); }
void setSource(const String16&, bool, v8::debug::LiveEditResult*) override {
UNREACHABLE();
}
bool isSourceLoadedLazily() const override { return true; }
const String16& source() const override {
return m_wasmTranslation->GetSource(m_id, m_functionIndex);

View File

@ -73,7 +73,7 @@ class V8DebuggerScript {
void setSourceURL(const String16&);
virtual void setSourceMappingURL(const String16&) = 0;
virtual void setSource(const String16& source, bool preview,
bool* stackChanged) = 0;
v8::debug::LiveEditResult* result) = 0;
virtual bool getPossibleBreakpoints(
const v8::debug::Location& start, const v8::debug::Location& end,

View File

@ -898,5 +898,43 @@ RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionFinished) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_LiveEditPatchScript) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSFunction, script_function, 0);
CONVERT_ARG_HANDLE_CHECKED(String, new_source, 1);
Handle<Script> script(Script::cast(script_function->shared()->script()),
isolate);
v8::debug::LiveEditResult result;
LiveEdit::PatchScript(script, new_source, &result);
switch (result.status) {
case v8::debug::LiveEditResult::COMPILE_ERROR:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: COMPILE_ERROR"));
case v8::debug::LiveEditResult::BLOCKED_BY_RUNNING_GENERATOR:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: BLOCKED_BY_RUNNING_GENERATOR"));
case v8::debug::LiveEditResult::BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME"));
case v8::debug::LiveEditResult::
BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME"));
case v8::debug::LiveEditResult::BLOCKED_BY_ACTIVE_FUNCTION:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: BLOCKED_BY_ACTIVE_FUNCTION"));
case v8::debug::LiveEditResult::BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME"));
case v8::debug::LiveEditResult::FRAME_RESTART_IS_NOT_SUPPORTED:
return isolate->Throw(*isolate->factory()->NewStringFromAsciiChecked(
"LiveEdit failed: FRAME_RESTART_IS_NOT_SUPPORTED"));
case v8::debug::LiveEditResult::OK:
return isolate->heap()->undefined_value();
}
return isolate->heap()->undefined_value();
}
} // namespace internal
} // namespace v8

View File

@ -155,7 +155,8 @@ namespace internal {
F(ScriptLocationFromLine, 4, 1) \
F(ScriptPositionInfo2, 3, 1) \
F(ScriptPositionInfo, 3, 1) \
F(SetGeneratorScopeVariableValue, 4, 1)
F(SetGeneratorScopeVariableValue, 4, 1) \
F(LiveEditPatchScript, 2, 1)
#define FOR_EACH_INTRINSIC_FORIN(F) \
F(ForInEnumerate, 1, 1) \

View File

@ -196,5 +196,301 @@ TEST(LiveEditTranslatePosition) {
CHECK_EQ(LiveEdit::TranslatePosition(changes, 6), 8);
CHECK_EQ(LiveEdit::TranslatePosition(changes, 8), 8);
}
namespace {
void PatchFunctions(v8::Local<v8::Context> context, const char* source_a,
const char* source_b,
v8::debug::LiveEditResult* result = nullptr) {
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Script> script_a =
v8::Script::Compile(context, v8_str(isolate, source_a)).ToLocalChecked();
script_a->Run(context).ToLocalChecked();
i::Handle<i::Script> i_script_a(
i::Script::cast(v8::Utils::OpenHandle(*script_a)->shared()->script()));
i::Isolate* i_isolate = i_script_a->GetIsolate();
if (result) {
LiveEdit::PatchScript(
i_script_a, i_isolate->factory()->NewStringFromAsciiChecked(source_b),
result);
} else {
v8::debug::LiveEditResult result;
LiveEdit::PatchScript(
i_script_a, i_isolate->factory()->NewStringFromAsciiChecked(source_b),
&result);
CHECK_EQ(result.status, v8::debug::LiveEditResult::OK);
}
}
} // anonymous namespace
// TODO(kozyatinskiy): enable it with new liveedit implementation.
DISABLED_TEST(LiveEditPatchFunctions) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
// Check that function is removed from compilation cache.
i::FLAG_allow_natives_syntax = true;
PatchFunctions(context, "42;", "%AbortJS('')");
PatchFunctions(context, "42;", "239;");
i::FLAG_allow_natives_syntax = false;
// Basic test cases.
PatchFunctions(context, "42;", "2;");
PatchFunctions(context, "42;", " 42;");
PatchFunctions(context, "42;", "42;");
// Trivial return value change.
PatchFunctions(context, "function foo() { return 1; }",
"function foo() { return 42; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
42);
// It is expected, we do not reevaluate top level function.
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var a = 3; function foo() { return a; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
1);
// Throw exception since var b is not defined in original source.
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var b = 4; function foo() { return b; }");
{
v8::TryCatch try_catch(env->GetIsolate());
CompileRun("foo()");
CHECK(try_catch.HasCaught());
}
// But user always can add new variable to function and use it.
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var b = 4; function foo() { var b = 5; return b; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
5);
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var b = 4; function foo() { var a = 6; return a; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
6);
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var d = (() => ({a:2}))(); function foo() { return d; }");
{
v8::TryCatch try_catch(env->GetIsolate());
CompileRun("foo()");
CHECK(try_catch.HasCaught());
}
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var b = 1; var a = 2; function foo() { return a; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
1);
PatchFunctions(context, "var a = 1; function foo() { return a; }",
"var b = 1; var a = 2; function foo() { return b; }");
{
v8::TryCatch try_catch(env->GetIsolate());
CompileRun("foo()");
CHECK(try_catch.HasCaught());
}
PatchFunctions(context, "function foo() { var a = 1; return a; }",
"function foo() { var b = 1; return b; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
1);
PatchFunctions(context, "var a = 3; function foo() { var a = 1; return a; }",
"function foo() { var b = 1; return a; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo()")
->ToInt32(env->GetIsolate())
->Value(),
3);
PatchFunctions(context, "var a = 3; var c = 7; function foo() { return a; }",
"var b = 5; var a = 3; function foo() { return b; }");
{
v8::TryCatch try_catch(env->GetIsolate());
CompileRun("foo()");
CHECK(try_catch.HasCaught());
}
// Add argument.
PatchFunctions(context, "function fooArgs(a1, b1) { return a1 + b1; }",
"function fooArgs(a2, b2, c2) { return a2 + b2 + c2; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "fooArgs(1,2,3)")
->ToInt32(env->GetIsolate())
->Value(),
6);
PatchFunctions(context, "function fooArgs(a1, b1) { return a1 + b1; }",
"function fooArgs(a1, b1, c1) { return a1 + b1 + c1; }");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "fooArgs(1,2,3)")
->ToInt32(env->GetIsolate())
->Value(),
6);
i::FLAG_allow_natives_syntax = true;
PatchFunctions(context,
"function foo(a, b) { return a + b; }; "
"%OptimizeFunctionOnNextCall(foo); foo(1,2);",
"function foo(a, b) { return a * b; };");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo(5,7)")
->ToInt32(env->GetIsolate())
->Value(),
35);
i::FLAG_allow_natives_syntax = false;
// Check inner function.
PatchFunctions(
context,
"function foo(a,b) { function op(a,b) { return a + b } return op(a,b); }",
"function foo(a,b) { function op(a,b) { return a * b } return op(a,b); "
"}");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "foo(8,9)")
->ToInt32(env->GetIsolate())
->Value(),
72);
// Update constructor.
PatchFunctions(context,
"class Foo { constructor(a,b) { this.data = a + b; } };",
"class Foo { constructor(a,b) { this.data = a * b; } };");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "new Foo(4,5).data")
->ToInt32(env->GetIsolate())
->Value(),
20);
// Change inner functions.
PatchFunctions(
context,
"function f(evt) { function f2() {} f2(),f3(); function f3() {} } "
"function f4() {}",
"function f(evt) { function f2() { return 1; } return f2() + f3(); "
"function f3() { return 2; } } function f4() {}");
CHECK_EQ(CompileRunChecked(env->GetIsolate(), "f()")
->ToInt32(env->GetIsolate())
->Value(),
3);
// Change usage of outer scope.
PatchFunctions(context,
"function ChooseAnimal(a, b) {\n "
" if (a == 7 && b == 7) {\n"
" return;\n"
" }\n"
" return function Chooser() {\n"
" return 'Cat' + a;\n"
" };\n"
"}\n"
"var old_closure = ChooseAnimal(2, 3);",
"function ChooseAnimal(a, b) {\n "
" if (a == 7 && b == 7) {\n"
" return;\n"
" }\n"
" return function Chooser() {\n"
" return 'Capybara' + b;\n"
" };\n"
"}\n");
CompileRunChecked(env->GetIsolate(), "var new_closure = ChooseAnimal(3, 4);");
v8::Local<v8::String> call_result =
CompileRunChecked(env->GetIsolate(), "new_closure()").As<v8::String>();
v8::String::Utf8Value new_result_utf8(env->GetIsolate(), call_result);
CHECK_NOT_NULL(strstr(*new_result_utf8, "Capybara4"));
call_result =
CompileRunChecked(env->GetIsolate(), "old_closure()").As<v8::String>();
v8::String::Utf8Value old_result_utf8(env->GetIsolate(), call_result);
CHECK_NOT_NULL(strstr(*old_result_utf8, "Cat2"));
// Update const literals.
PatchFunctions(context, "function foo() { return 'a' + 'b'; }",
"function foo() { return 'c' + 'b'; }");
{
v8::Local<v8::String> result =
CompileRunChecked(env->GetIsolate(), "foo()").As<v8::String>();
v8::String::Utf8Value new_result_utf8(env->GetIsolate(), result);
CHECK_NOT_NULL(strstr(*new_result_utf8, "cb"));
}
}
// TODO(kozyatinskiy): enable it with new liveedit implementation.
DISABLED_TEST(LiveEditCompileError) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
debug::LiveEditResult result;
PatchFunctions(
context,
"var something1 = 25; \n"
" function ChooseAnimal() { return 'Cat'; } \n"
" ChooseAnimal.Helper = function() { return 'Help!'; }\n",
"var something1 = 25; \n"
" function ChooseAnimal() { return 'Cap' + ) + 'bara'; "
"} \n"
" ChooseAnimal.Helper = function() { return 'Help!'; }\n",
&result);
CHECK_EQ(result.status, debug::LiveEditResult::COMPILE_ERROR);
CHECK_EQ(result.line_number, 2);
CHECK_EQ(result.column_number, 51);
v8::String::Utf8Value result_message(env->GetIsolate(), result.message);
CHECK_NOT_NULL(
strstr(*result_message, "Uncaught SyntaxError: Unexpected token )"));
{
v8::Local<v8::String> result =
CompileRunChecked(env->GetIsolate(), "ChooseAnimal()").As<v8::String>();
v8::String::Utf8Value new_result_utf8(env->GetIsolate(), result);
CHECK_NOT_NULL(strstr(*new_result_utf8, "Cat"));
}
PatchFunctions(context, "function foo() {}",
"function foo() { return a # b; }", &result);
CHECK_EQ(result.status, debug::LiveEditResult::COMPILE_ERROR);
// TODO(kozyatinskiy): should be 1.
CHECK_EQ(result.line_number, kNoSourcePosition);
// TODO(kozyatinskiy): should be 26.
CHECK_EQ(result.column_number, kNoSourcePosition);
}
TEST(LiveEditFunctionExpression) {
const char* original_source =
"(function() {\n "
" return 'Cat';\n"
"})\n";
const char* updated_source =
"(function() {\n "
" return 'Capy' + 'bara';\n"
"})\n";
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::Local<v8::Context> context = env.local();
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::Script> script =
v8::Script::Compile(context, v8_str(isolate, original_source))
.ToLocalChecked();
v8::Local<v8::Function> f =
script->Run(context).ToLocalChecked().As<v8::Function>();
i::Handle<i::Script> i_script(
i::Script::cast(v8::Utils::OpenHandle(*script)->shared()->script()));
i::Isolate* i_isolate = i_script->GetIsolate();
debug::LiveEditResult result;
LiveEdit::PatchScript(
i_script, i_isolate->factory()->NewStringFromAsciiChecked(updated_source),
&result);
CHECK_EQ(result.status, debug::LiveEditResult::OK);
{
v8::Local<v8::String> result =
f->Call(context, context->Global(), 0, nullptr)
.ToLocalChecked()
.As<v8::String>();
v8::String::Utf8Value new_result_utf8(env->GetIsolate(), result);
CHECK_NOT_NULL(strstr(*new_result_utf8, "Capybara"));
}
}
} // namespace internal
} // namespace v8

View File

@ -25,6 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -34,13 +35,11 @@ eval("var something1 = 25; "
assertEquals("Cat", ChooseAnimal());
var script = Debug.findScript(ChooseAnimal);
var orig_animal = "Cat";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "Cap' + 'y' + 'bara";
var new_source =
Debug.scriptSource(ChooseAnimal).replace(orig_animal, new_animal_patch);
var change_log = new Array();
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
%LiveEditPatchScript(ChooseAnimal, new_source);
assertEquals("Capybara", ChooseAnimal());

View File

@ -25,8 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --noalways-opt
// Flags: --noalways-opt --allow-natives-syntax
Debug = debug.Debug
@ -43,19 +42,12 @@ var old_closure = ChooseAnimal(19);
assertEquals("Cat", old_closure());
var script = Debug.findScript(ChooseAnimal);
var orig_animal = "'Cat'";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "'Capybara' + p";
// We patch innermost function "Chooser".
// However, this does not actually patch existing "Chooser" instances,
// because old value of parameter "p" was not saved.
// Instead it patches ChooseAnimal.
var change_log = new Array();
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
print("Change log: " + JSON.stringify(change_log) + "\n");
%LiveEditPatchScript(
ChooseAnimal, Debug.scriptSource(ChooseAnimal).replace("'Cat'", "'Capybara' + p"));
var new_closure = ChooseAnimal(19);
// New instance of closure is patched.

View File

@ -30,6 +30,7 @@
// hasen't been changed. However actually function became one level more nested
// and must be recompiled because it uses variable from outer scope.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -49,15 +50,11 @@ eval(
var z6 = Factory(6);
assertEquals(8, z6());
var script = Debug.findScript(Factory);
var new_source = Debug.scriptSource(Factory).replace(
function_z_text,
'function Intermediate() {\nreturn (\n' + function_z_text + ')\n;\n}\n');
var new_source = script.source.replace(function_z_text, "function Intermediate() {\nreturn (\n" + function_z_text + ")\n;\n}\n");
print("new source: " + new_source);
var change_log = new Array();
var result = Debug.LiveEdit.SetScriptSource(script, new_source, false, change_log);
print("Result: " + JSON.stringify(result) + "\n");
print("Change log: " + JSON.stringify(change_log) + "\n");
%LiveEditPatchScript(Factory, new_source);
assertEquals(8, z6());

View File

@ -31,6 +31,7 @@
// hasen't been changed. However actually function became one level more nested
// and must be recompiled because it uses variable from outer scope.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -53,14 +54,10 @@ var res = TestFunction();
print(res);
assertEquals('a,c', res);
var script = Debug.findScript(TestFunction);
var new_source = script.source.replace("2013", "b");
var new_source = Debug.scriptSource(TestFunction).replace('2013', 'b');
print("new source: " + new_source);
var change_log = new Array();
var result = Debug.LiveEdit.SetScriptSource(script, new_source, false, change_log);
print("Result: " + JSON.stringify(result) + "\n");
print("Change log: " + JSON.stringify(change_log) + "\n");
%LiveEditPatchScript(TestFunction, new_source);
var res = TestFunction();
print(res);

View File

@ -25,6 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -33,35 +34,26 @@ unique_id = 1;
function TestBase(name) {
print("TestBase constructor: " + name);
this.ChooseAnimal = eval(
"/* " + unique_id + "*/\n" +
"(function ChooseAnimal(callback) {\n " +
" callback();\n" +
" return 'Cat';\n" +
"})\n"
);
const original_source = '/* ' + unique_id + '*/\n' +
'(function ChooseAnimal(callback) {\n ' +
' callback();\n' +
' return \'Cat\';\n' +
'})\n';
const updated_source = original_source.replace('\'Cat\'', '\'Capybara\'');
this.ChooseAnimal = eval(original_source);
// Prevents eval script caching.
unique_id++;
var script = Debug.findScript(this.ChooseAnimal);
var orig_animal = "'Cat'";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "'Capybara'";
const func = this.ChooseAnimal;
var got_exception = false;
var successfully_changed = false;
// Should be called from Debug context.
this.ScriptChanger = function() {
this.ScriptChanger = () => {
assertEquals(false, successfully_changed, "applying patch second time");
// Runs in debugger context.
var change_log = new Array();
try {
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
} finally {
print("Change log: " + JSON.stringify(change_log) + "\n");
}
%LiveEditPatchScript(func, updated_source);
successfully_changed = true;
};
}
@ -74,7 +66,7 @@ function WrapInCatcher(f, holder) {
try {
f();
} catch (e) {
if (e instanceof Debug.LiveEdit.Failure) {
if (e.startsWith('LiveEdit failed')) {
holder[0] = e;
} else {
throw e;

View File

@ -25,6 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -34,23 +35,16 @@ eval("var something1 = 25; \n"
assertEquals("Cat", ChooseAnimal());
var script = Debug.findScript(ChooseAnimal);
var new_source =
Debug.scriptSource(ChooseAnimal).replace('Cat', 'Cap\' + ) + \'bara');
print('new source: ' + new_source);
var orig_animal = "Cat";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "Cap' + ) + 'bara";
var change_log = new Array();
var caught_exception = null;
try {
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos,
orig_animal.length, new_animal_patch, change_log);
%LiveEditPatchScript(ChooseAnimal, new_source);
} catch (e) {
caught_exception = e;
}
assertNotNull(caught_exception);
assertEquals("Unexpected token )",
caught_exception.details.syntaxErrorMessage);
assertEquals(2, caught_exception.details.position.start.line);
assertEquals('LiveEdit failed: COMPILE_ERROR', caught_exception);

View File

@ -92,8 +92,7 @@ function TestCase(test_scenario, expected_output) {
}
script_text_generator.change(change_var);
try {
Debug.LiveEdit.SetScriptSource(script, script_text_generator.get(),
false, []);
%LiveEditPatchScript(test_fun, script_text_generator.get())
} catch (e) {
print("LiveEdit exception: " + e);
throw e;

View File

@ -2,9 +2,9 @@
// 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 BestEditor() {
throw 'Emacs';
}
@ -37,11 +37,8 @@ function listener(event, exec_state, event_data, data) {
};
function Replace(fun, original, patch) {
var script = Debug.findScript(fun);
if (fun.toString().indexOf(original) < 0) return;
var patch_pos = script.source.indexOf(original);
var change_log = [];
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, original.length, patch, change_log);
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
}
Debug.setListener(listener);

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file
// Flags: --allow-natives-syntax --enable-inspector
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -17,14 +17,10 @@ assertEquals("Cat", foo());
foo();
var script = Debug.findScript(ChooseAnimal);
var new_source =
Debug.scriptSource(ChooseAnimal).replace('Cat', "Cap' + 'y' + 'bara");
print('new source: ' + new_source);
var orig_animal = "Cat";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "Cap' + 'y' + 'bara";
var change_log = new Array();
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
%LiveEditPatchScript(ChooseAnimal, new_source);
assertEquals("Capybara", foo());

View File

@ -25,6 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -46,14 +47,9 @@ function Test(old_expression, new_expression) {
assertEquals("Cat", ChooseAnimalArray[i]());
}
var script = Debug.findScript(ChooseAnimalArray[0]);
var patch_pos = script.source.indexOf(old_expression);
var new_animal_patch = new_expression;
var change_log = new Array();
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos,
old_expression.length, new_expression, change_log);
var new_source =
Debug.scriptSource(ChooseAnimalArray[0]).replace(old_expression, new_expression);
%LiveEditPatchScript(ChooseAnimalArray[0], new_source);
for (var i = 0; i < ChooseAnimalArray.length; i++) {
assertEquals("Capybara", ChooseAnimalArray[i]());

View File

@ -25,6 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
Debug = debug.Debug
@ -47,9 +48,7 @@ eval("var something1 = 25; \n"
assertEquals("Cat", ChooseAnimal());
assertEquals(25, something1);
var script = Debug.findScript(ChooseAnimal);
var new_source = script.source.replace("Cat", "Cap' + 'yb' + 'ara");
var new_source = Debug.scriptSource(ChooseAnimal).replace("Cat", "Cap' + 'yb' + 'ara");
var new_source = new_source.replace("25", "26");
var new_source = new_source.replace("Help", "Hello");
var new_source = new_source.replace("17", "18");
@ -62,10 +61,7 @@ var new_source = new_source.replace("17", "18");
var new_source = new_source.replace("// Array", "Array");
print("new source: " + new_source);
var change_log = new Array();
var result = Debug.LiveEdit.SetScriptSource(script, new_source, false, change_log);
print("Result: " + JSON.stringify(result) + "\n");
print("Change log: " + JSON.stringify(change_log) + "\n");
%LiveEditPatchScript(ChooseAnimal, new_source);
assertEquals("Capybara", ChooseAnimal());
// Global variable do not get changed (without restarting script).

View File

@ -34,6 +34,7 @@
// change they are 114 characters away from each other. New instance of Code is
// required when those numbers cross the border value of 64 (in any direction).
// Flags: --allow-natives-syntax
Debug = debug.Debug
eval(
@ -46,19 +47,13 @@ eval(
"}"
);
var script = Debug.findScript(BeingReplaced);
var orig_body = "{}";
var patch_pos = script.source.indexOf(orig_body);
// Line long enough to change rinfo encoding.
var new_body_patch = "{return 'Capybara';" +
" " +
"}";
var change_log = new Array();
function Changer() {
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, orig_body.length, new_body_patch, change_log);
print("Change log: " + JSON.stringify(change_log) + "\n");
// Line long enough to change rinfo encoding.
var new_source =
Debug.scriptSource(BeingReplaced).replace("{}", "{return 'Capybara';" +
" " +
"}");
%LiveEditPatchScript(BeingReplaced, new_source);
}
function NoOp() {

View File

@ -2,25 +2,23 @@
// 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
var counter = 0;
var exception = null;
function f() {
if (++counter > 5) return;
debugger;
return counter;
}
};
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Break) return;
try {
var script = Debug.findScript(f);
var original = 'debugger;';
var patch = 'debugger;\n';
var position = script.source.indexOf(original);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(
script, position, original.length, patch, []);
%LiveEditPatchScript(f, Debug.scriptSource(f).replace(original, patch));
} catch (e) {
exception = e;
}

View File

@ -25,22 +25,20 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax
Debug = debug.Debug;
Debug.setListener(listener);
SlimFunction = eval(
"(function() {\n " +
" return 'Cat';\n" +
"})\n"
);
'(function f() {\n ' +
' return \'Cat\';\n' +
'})\n');
var script = Debug.findScript(SlimFunction);
Debug.setScriptBreakPointById(script.id, 1, 0);
var orig_animal = "'Cat'";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "'Capybara'";
debugger_handler = (function() {
@ -50,14 +48,8 @@ debugger_handler = (function() {
return;
}
already_called = true;
var change_log = new Array();
try {
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos,
orig_animal.length, new_animal_patch, change_log);
} finally {
print("Change log: " + JSON.stringify(change_log) + "\n");
}
%LiveEditPatchScript(
SlimFunction, script.source.replace(orig_animal, new_animal_patch));
};
})();

View File

@ -2,9 +2,9 @@
// 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 BestEditor() {
return 'Emacs';
}
@ -39,11 +39,8 @@ function listener(event, exec_state, event_data, data) {
};
function Replace(fun, original, patch) {
var script = Debug.findScript(fun);
if (fun.toString().indexOf(original) < 0) return;
var patch_pos = script.source.indexOf(original);
var change_log = [];
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(script, patch_pos, original.length, patch, change_log);
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
}
Debug.setListener(listener);
@ -56,7 +53,7 @@ Debug.setListener(null);
assertNull(exception);
assertEquals(["Emacs", "Eclipse", "Vim"], results);
print(JSON.stringify(log, 1));
// TODO(kozyatinskiy): uncomment lines with new liveedit implementation.
assertEquals([
"debugger;",
"results.push(BestEditor());",
@ -65,12 +62,12 @@ assertEquals([
"results.push(BestEditor());",
"results.push(BestEditor());",
" return 'Emacs';",
" return 'Eclipse';",
// " return 'Eclipse';",
" return 'Eclipse';",
"results.push(BestEditor());",
"results.push(BestEditor());",
" return 'Eclipse';",
" return 'Vim';",
// " return 'Vim';",
" return 'Vim';",
"results.push(BestEditor());",
"Debug.setListener(null);"

View File

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Test that live-editing a frame that uses new.target fails.
// Flags: --allow-natives-syntax
Debug = debug.Debug
var calls = 0;
@ -43,16 +43,11 @@ function ExecuteInDebugContext(f) {
function Replace(fun, original, patch) {
ExecuteInDebugContext(function() {
var change_log = [];
try {
var script = Debug.findScript(fun);
var patch_pos = script.source.indexOf(original);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(
script, patch_pos, original.length, patch, change_log);
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
} catch (e) {
assertEquals("BLOCKED_NO_NEW_TARGET_ON_RESTART",
change_log[0].functions_on_stack[0].replace_problem);
assertInstanceof(e, Debug.LiveEdit.Failure);
// TODO(kozyatinskiy): message should be BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME.
assertEquals(e, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
exceptions++;
}
});

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
// Test that live-editing a frame to introduce new.target fails.
Debug = debug.Debug
@ -40,16 +40,11 @@ function ExecuteInDebugContext(f) {
function Replace(fun, original, patch) {
ExecuteInDebugContext(function() {
var change_log = [];
try {
var script = Debug.findScript(fun);
var patch_pos = script.source.indexOf(original);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(
script, patch_pos, original.length, patch, change_log);
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
} catch (e) {
assertEquals("BLOCKED_NO_NEW_TARGET_ON_RESTART",
change_log[0].functions_on_stack[0].replace_problem);
assertInstanceof(e, Debug.LiveEdit.Failure);
// TODO(kozyatinskiy): message should be BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME.
assertEquals(e, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
exceptions++;
}
});

View File

@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Test that live-editing a frame above one that uses new.target succeeds.
// Flags: --allow-natives-syntax
Debug = debug.Debug
Debug = debug.Debug;
var wrapper_calls = 0;
var construct_calls = 0;
var exceptions = 0;
@ -47,10 +47,7 @@ function Replace(fun, original, patch) {
ExecuteInDebugContext(function() {
var change_log = [];
try {
var script = Debug.findScript(fun);
var patch_pos = script.source.indexOf(original);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(
script, patch_pos, original.length, patch, change_log);
%LiveEditPatchScript(fun, Debug.scriptSource(fun).replace(original, patch));
} catch (e) {
exceptions++;
}

View File

@ -2,9 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
var Debug = debug.Debug;
var LiveEdit = Debug.LiveEdit;
unique_id = 0;
@ -62,15 +61,8 @@ function ExecuteInDebugContext(f) {
function patch(fun, from, to) {
function debug() {
var log = new Array();
var script = Debug.findScript(fun);
var pos = script.source.indexOf(from);
try {
LiveEdit.TestApi.ApplySingleChunkPatch(script, pos, from.length, to,
log);
} finally {
print("Change log: " + JSON.stringify(log) + "\n");
}
%LiveEditPatchScript(fun, script.source.replace(from, to));
}
ExecuteInDebugContext(debug);
}
@ -84,8 +76,9 @@ function patch(fun, from, to) {
function attempt_gen_patch() {
assertFalse(gen_patch_attempted);
gen_patch_attempted = true;
assertThrows(function() { patch(generator, "'Cat'", "'Capybara'") },
LiveEdit.Failure);
assertThrowsEquals(function() {
patch(generator, '\'Cat\'', '\'Capybara\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
};
var iter = generator(attempt_gen_patch);
assertIteratorResult(undefined, false, iter.next());
@ -104,8 +97,10 @@ function patch(fun, from, to) {
// Patching will fail however when a live iterator is suspended.
iter = generator(function(){});
assertIteratorResult(undefined, false, iter.next());
assertThrows(function() { patch(generator, "'Capybara'", "'Tapir'") },
LiveEdit.Failure);
// TODO(kozyatinskiy): message should be BLOCKED_BY_RUNNING_GENERATOR.
assertThrowsEquals(function() {
patch(generator, '\'Capybara\'', '\'Tapir\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
assertIteratorResult("Capybara", true, iter.next());
// Try to patch functions with activations inside and outside generator
@ -123,8 +118,9 @@ function patch(fun, from, to) {
}
fun_patch_attempted = true;
// Patching outside a generator activation must fail.
assertThrows(function() { patch(fun_outside, "'Cat'", "'Cobra'") },
LiveEdit.Failure);
assertThrowsEquals(function() {
patch(fun_outside, '\'Cat\'', '\'Cobra\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
// Patching inside a generator activation may succeed.
patch(fun_inside, "'Cat'", "'Koala'");
}

View File

@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
var Debug = debug.Debug;
var LiveEdit = Debug.LiveEdit;
unique_id = 0;
@ -69,16 +70,8 @@ function ExecuteInDebugContext(f) {
function patch(fun, from, to) {
function debug() {
var log = new Array();
var script = Debug.findScript(fun);
var pos = script.source.indexOf(from);
print(`pos ${pos}`);
try {
LiveEdit.TestApi.ApplySingleChunkPatch(script, pos, from.length, to,
log);
} finally {
print("Change log: " + JSON.stringify(log) + "\n");
}
%LiveEditPatchScript(fun, script.source.replace(from, to));
}
ExecuteInDebugContext(debug);
}
@ -92,8 +85,9 @@ function patch(fun, from, to) {
function attempt_patch() {
assertFalse(patch_attempted);
patch_attempted = true;
assertThrows(function() { patch(asyncfn, "'Cat'", "'Capybara'") },
LiveEdit.Failure);
assertThrowsEquals(function() {
patch(asyncfn, '\'Cat\'', '\'Capybara\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
};
var promise = asyncfn(attempt_patch);
// Patch should not succeed because there is a live async function activation
@ -112,15 +106,17 @@ function patch(fun, from, to) {
// Patching will fail however when an async function is suspended.
var resolve;
promise = asyncfn(function(){return new Promise(function(r){resolve = r})});
assertThrows(function() { patch(asyncfn, "'Capybara'", "'Tapir'") },
LiveEdit.Failure);
// TODO(kozyatinskiy): message should be BLOCKED_BY_RUNNING_GENERATOR.
assertThrowsEquals(function() {
patch(asyncfn, '\'Capybara\'', '\'Tapir\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
resolve();
assertPromiseValue("Capybara", promise);
// Try to patch functions with activations inside and outside async
// function activations. We should succeed in the former case, but not in the
// latter.
var fun_outside = MakeFunction();
var fun_outside = eval('((callback) => { callback(); return \'Cat\';})');
var fun_inside = MakeFunction();
var fun_patch_attempted = false;
var fun_patch_restarted = false;
@ -132,15 +128,18 @@ function patch(fun, from, to) {
}
fun_patch_attempted = true;
// Patching outside an async function activation must fail.
assertThrows(function() { patch(fun_outside, "'Cat'", "'Cobra'") },
LiveEdit.Failure);
assertThrowsEquals(function() {
patch(fun_outside, '\'Cat\'', '\'Cobra\'')
}, 'LiveEdit failed: BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME');
// Patching inside an async function activation may succeed.
patch(fun_inside, "'Cat'", "'Koala'");
}
promise = asyncfn(function() { return fun_inside(attempt_fun_patches) });
result = fun_outside(() => asyncfn(function() {
return fun_inside(attempt_fun_patches);
}));
assertEquals('Cat',
fun_outside(function () {
assertPromiseValue('Capybara', promise);
assertEquals(result, 'Cat');
assertTrue(fun_patch_restarted);
assertTrue(fun_inside.toString().includes("'Koala'"));
}));

View File

@ -311,11 +311,6 @@ class DebugWrapper {
return scopes;
}
get LiveEdit() {
const debugContext = %GetDebugContext();
return debugContext.Debug.LiveEdit;
}
// --- Internal methods. -----------------------------------------------------
getNextMessageId() {

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --no-always-opt
let {session, contextGroup, Protocol} =
InspectorTest.start('Tests Debugger.setScriptSource');

View File

@ -11,9 +11,13 @@ Running test: testIncorrectScriptId
Running test: testSourceWithSyntaxError
{
error : {
code : -32000
message : Uncaught [object Object]
}
id : <messageId>
result : {
exceptionDetails : {
columnNumber : 0
exceptionId : <exceptionId>
lineNumber : 0
text : Invalid or unexpected token
}
}
}

View File

@ -10,9 +10,9 @@ TestExpression(2,4) === 8
Update current script source 'a * b' -> 'a # b'..
{
exceptionDetails : {
columnNumber : 13
columnNumber : 0
exceptionId : <exceptionId>
lineNumber : 1
lineNumber : 0
text : Invalid or unexpected token
}
}