[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:
caitpotter88 2016-06-15 05:22:58 -07:00 committed by Commit bot
parent a5dd1c4631
commit fd4d385b6d
3 changed files with 166 additions and 2 deletions

View File

@ -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.

View File

@ -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;
}
}
}
}

View 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();