[liveedit]: fail to patch if target is outside of async function on stack
If an active generator is found on the stack (FUNCTION_BLOCKED_UNDER_GENERATOR), and the target function is not found on top of that generator, add the error. Based on test by wingo@igalia.com and littledan@chromium.org (https://codereview.chromium.org/2035643003/) LOG=N BUG=v8:4483 R=yangguo@chromium.org, littledan@chromium.org Review-Url: https://codereview.chromium.org/2058733002 Cr-Commit-Position: refs/heads/master@{#37000}
This commit is contained in:
parent
a5dd1c4631
commit
fd4d385b6d
@ -4503,8 +4503,17 @@ BUILTIN(GeneratorFunctionConstructor) {
|
||||
|
||||
BUILTIN(AsyncFunctionConstructor) {
|
||||
HandleScope scope(isolate);
|
||||
RETURN_RESULT_OR_FAILURE(
|
||||
isolate, CreateDynamicFunction(isolate, args, "async function"));
|
||||
Handle<JSFunction> func;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, func, CreateDynamicFunction(isolate, args, "async function"));
|
||||
|
||||
// Do not lazily compute eval position for AsyncFunction, as they may not be
|
||||
// determined after the function is resumed.
|
||||
Handle<Script> script = handle(Script::cast(func->shared()->script()));
|
||||
int position = script->GetEvalPosition();
|
||||
USE(position);
|
||||
|
||||
return *func;
|
||||
}
|
||||
|
||||
// ES6 section 19.4.1.1 Symbol ( [ description ] ) for the [[Call]] case.
|
||||
|
@ -1627,6 +1627,21 @@ class MultipleFunctionTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_status(LiveEdit::FunctionPatchabilityStatus status) {
|
||||
Isolate* isolate = old_shared_array_->GetIsolate();
|
||||
int len = GetArrayLength(old_shared_array_);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
Handle<Object> old_element =
|
||||
JSReceiver::GetElement(isolate, result_, i).ToHandleChecked();
|
||||
if (!old_element->IsSmi() ||
|
||||
Smi::cast(*old_element)->value() ==
|
||||
LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH) {
|
||||
SetElementSloppy(result_, i,
|
||||
Handle<Smi>(Smi::FromInt(status), isolate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Handle<JSArray> old_shared_array_;
|
||||
Handle<JSArray> new_shared_array_;
|
||||
@ -1704,6 +1719,13 @@ static const char* DropActivationsInActiveThreadImpl(Isolate* isolate,
|
||||
// Fail.
|
||||
return NULL;
|
||||
}
|
||||
if (non_droppable_reason ==
|
||||
LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR &&
|
||||
!target_frame_found) {
|
||||
// Fail.
|
||||
target.set_status(non_droppable_reason);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
133
test/mjsunit/harmony/debug-async-liveedit.js
Normal file
133
test/mjsunit/harmony/debug-async-liveedit.js
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2016 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: --harmony-async-await
|
||||
// Flags: --expose-debug-as debug --allow-natives-syntax --ignition-generators
|
||||
|
||||
var Debug = debug.Debug;
|
||||
var LiveEdit = Debug.LiveEdit;
|
||||
|
||||
unique_id = 0;
|
||||
|
||||
var AsyncFunction = (async function(){}).constructor;
|
||||
|
||||
function assertPromiseValue(value, promise) {
|
||||
promise.then(resolve => {
|
||||
went = true;
|
||||
if (resolve !== value) {
|
||||
print(`expected ${value} found ${resolve}`);
|
||||
quit(1);
|
||||
}
|
||||
}, reject => {
|
||||
print(`rejected ${reject}`);
|
||||
quit(1);
|
||||
});
|
||||
}
|
||||
|
||||
function MakeAsyncFunction() {
|
||||
// Prevents eval script caching.
|
||||
unique_id++;
|
||||
return AsyncFunction('callback',
|
||||
"/* " + unique_id + "*/\n" +
|
||||
"await callback();\n" +
|
||||
"return 'Cat';\n");
|
||||
}
|
||||
|
||||
function MakeFunction() {
|
||||
// Prevents eval script caching.
|
||||
unique_id++;
|
||||
return Function('callback',
|
||||
"/* " + unique_id + "*/\n" +
|
||||
"callback();\n" +
|
||||
"return 'Cat';\n");
|
||||
}
|
||||
|
||||
// First, try MakeGenerator with no perturbations.
|
||||
(function(){
|
||||
var asyncfn = MakeAsyncFunction();
|
||||
function callback() {};
|
||||
var promise = asyncfn(callback);
|
||||
assertPromiseValue('Cat', promise);
|
||||
})();
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
%ExecuteInDebugContext(debug);
|
||||
}
|
||||
|
||||
// Try to edit a MakeAsyncFunction while it's running, then again while it's
|
||||
// stopped.
|
||||
(function(){
|
||||
var asyncfn = MakeAsyncFunction();
|
||||
|
||||
var patch_attempted = false;
|
||||
function attempt_patch() {
|
||||
assertFalse(patch_attempted);
|
||||
patch_attempted = true;
|
||||
assertThrows(function() { patch(asyncfn, "'Cat'", "'Capybara'") },
|
||||
LiveEdit.Failure);
|
||||
};
|
||||
var promise = asyncfn(attempt_patch);
|
||||
// Patch should not succeed because there is a live async function activation
|
||||
// on the stack.
|
||||
assertPromiseValue("Cat", promise);
|
||||
assertTrue(patch_attempted);
|
||||
|
||||
%RunMicrotasks();
|
||||
|
||||
// At this point one iterator is live, but closed, so the patch will succeed.
|
||||
patch(asyncfn, "'Cat'", "'Capybara'");
|
||||
promise = asyncfn(function(){});
|
||||
// Patch successful.
|
||||
assertPromiseValue("Capybara", promise);
|
||||
|
||||
// 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);
|
||||
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_inside = MakeFunction();
|
||||
var fun_patch_attempted = false;
|
||||
var fun_patch_restarted = false;
|
||||
function attempt_fun_patches() {
|
||||
if (fun_patch_attempted) {
|
||||
assertFalse(fun_patch_restarted);
|
||||
fun_patch_restarted = true;
|
||||
return;
|
||||
}
|
||||
fun_patch_attempted = true;
|
||||
// Patching outside an async function activation must fail.
|
||||
assertThrows(function() { patch(fun_outside, "'Cat'", "'Cobra'") },
|
||||
LiveEdit.Failure);
|
||||
// Patching inside an async function activation may succeed.
|
||||
patch(fun_inside, "'Cat'", "'Koala'");
|
||||
}
|
||||
promise = asyncfn(function() { return fun_inside(attempt_fun_patches) });
|
||||
assertEquals('Cat',
|
||||
fun_outside(function () {
|
||||
assertPromiseValue('Capybara', promise);
|
||||
assertTrue(fun_patch_restarted);
|
||||
assertTrue(fun_inside.toString().includes("'Koala'"));
|
||||
}));
|
||||
})();
|
||||
|
||||
%RunMicrotasks();
|
Loading…
Reference in New Issue
Block a user