Allow access to scopes of suspended generator objects

The scopes of suspended generators can now be accessed through GeneratorMirror
(similar to FrameMirror).

BUG=v8:5235

Review-Url: https://codereview.chromium.org/2228393002
Cr-Commit-Position: refs/heads/master@{#38530}
This commit is contained in:
jgruber 2016-08-10 04:14:19 -07:00 committed by Commit bot
parent a91811e192
commit 4cff8218b8
6 changed files with 573 additions and 9 deletions

View File

@ -125,6 +125,19 @@ ScopeIterator::ScopeIterator(Isolate* isolate, Handle<JSFunction> function)
UnwrapEvaluationContext();
}
ScopeIterator::ScopeIterator(Isolate* isolate,
Handle<JSGeneratorObject> generator)
: isolate_(isolate),
frame_inspector_(NULL),
context_(generator->context()),
seen_script_scope_(false),
failed_(false) {
if (!generator->function()->shared()->IsSubjectToDebugging()) {
context_ = Handle<Context>();
}
UnwrapEvaluationContext();
}
void ScopeIterator::UnwrapEvaluationContext() {
while (true) {
if (context_.is_null()) return;
@ -623,6 +636,7 @@ bool ScopeIterator::SetParameterValue(Handle<ScopeInfo> scope_info,
bool ScopeIterator::SetStackVariableValue(Handle<ScopeInfo> scope_info,
Handle<String> variable_name,
Handle<Object> new_value) {
if (frame_inspector_ == nullptr) return false;
JavaScriptFrame* frame = GetFrame();
// Setting stack locals of optimized frames is not supported.
if (frame->is_optimized()) return false;

View File

@ -43,6 +43,7 @@ class ScopeIterator {
Option options = DEFAULT);
ScopeIterator(Isolate* isolate, Handle<JSFunction> function);
ScopeIterator(Isolate* isolate, Handle<JSGeneratorObject> generator);
MUST_USE_RESULT MaybeHandle<JSObject> MaterializeScopeDetails();

View File

@ -1022,7 +1022,7 @@ FunctionMirror.prototype.scopeCount = function() {
FunctionMirror.prototype.scope = function(index) {
if (this.resolved()) {
return new ScopeMirror(UNDEFINED, this, index);
return new ScopeMirror(UNDEFINED, this, UNDEFINED, index);
}
};
@ -1451,6 +1451,27 @@ GeneratorMirror.prototype.receiver = function() {
};
GeneratorMirror.prototype.scopeCount = function() {
// This value can change over time as the underlying generator is suspended
// at different locations.
return %GetGeneratorScopeCount(this.value());
};
GeneratorMirror.prototype.scope = function(index) {
return new ScopeMirror(UNDEFINED, UNDEFINED, this, index);
};
GeneratorMirror.prototype.allScopes = function() {
var scopes = [];
for (let i = 0; i < this.scopeCount(); i++) {
scopes.push(this.scope(i));
}
return scopes;
};
/**
* Base mirror object for properties.
* @param {ObjectMirror} mirror The mirror object having this property
@ -1973,7 +1994,7 @@ FrameMirror.prototype.scopeCount = function() {
FrameMirror.prototype.scope = function(index) {
return new ScopeMirror(this, UNDEFINED, index);
return new ScopeMirror(this, UNDEFINED, UNDEFINED, index);
};
@ -1984,7 +2005,8 @@ FrameMirror.prototype.allScopes = function(opt_ignore_nested_scopes) {
!!opt_ignore_nested_scopes);
var result = [];
for (var i = 0; i < scopeDetails.length; ++i) {
result.push(new ScopeMirror(this, UNDEFINED, i, scopeDetails[i]));
result.push(new ScopeMirror(this, UNDEFINED, UNDEFINED, i,
scopeDetails[i]));
}
return result;
};
@ -2163,7 +2185,7 @@ var kScopeDetailsStartPositionIndex = 3;
var kScopeDetailsEndPositionIndex = 4;
var kScopeDetailsFunctionIndex = 5;
function ScopeDetails(frame, fun, index, opt_details) {
function ScopeDetails(frame, fun, gen, index, opt_details) {
if (frame) {
this.break_id_ = frame.break_id_;
this.details_ = opt_details ||
@ -2173,10 +2195,15 @@ function ScopeDetails(frame, fun, index, opt_details) {
index);
this.frame_id_ = frame.details_.frameId();
this.inlined_frame_id_ = frame.details_.inlinedFrameIndex();
} else {
} else if (fun) {
this.details_ = opt_details || %GetFunctionScopeDetails(fun.value(), index);
this.fun_value_ = fun.value();
this.break_id_ = UNDEFINED;
} else {
this.details_ =
opt_details || %GetGeneratorScopeDetails(gen.value(), index);
this.gen_value_ = gen.value();
this.break_id_ = UNDEFINED;
}
this.index_ = index;
}
@ -2235,9 +2262,12 @@ ScopeDetails.prototype.setVariableValueImpl = function(name, new_value) {
%CheckExecutionState(this.break_id_);
raw_res = %SetScopeVariableValue(this.break_id_, this.frame_id_,
this.inlined_frame_id_, this.index_, name, new_value);
} else {
} else if (!IS_UNDEFINED(this.fun_value_)) {
raw_res = %SetScopeVariableValue(this.fun_value_, null, null, this.index_,
name, new_value);
} else {
raw_res = %SetScopeVariableValue(this.gen_value_, null, null, this.index_,
name, new_value);
}
if (!raw_res) throw %make_error(kDebugger, "Failed to set variable value");
};
@ -2248,12 +2278,13 @@ ScopeDetails.prototype.setVariableValueImpl = function(name, new_value) {
* be specified.
* @param {FrameMirror} frame The frame this scope is a part of
* @param {FunctionMirror} function The function this scope is a part of
* @param {GeneratorMirror} gen The generator this scope is a part of
* @param {number} index The scope index in the frame
* @param {Array=} opt_details Raw scope details data
* @constructor
* @extends Mirror
*/
function ScopeMirror(frame, fun, index, opt_details) {
function ScopeMirror(frame, fun, gen, index, opt_details) {
%_Call(Mirror, this, MirrorType.SCOPE_TYPE);
if (frame) {
this.frame_index_ = frame.index_;
@ -2261,7 +2292,7 @@ function ScopeMirror(frame, fun, index, opt_details) {
this.frame_index_ = UNDEFINED;
}
this.scope_index_ = index;
this.details_ = new ScopeDetails(frame, fun, index, opt_details);
this.details_ = new ScopeDetails(frame, fun, gen, index, opt_details);
}
inherits(ScopeMirror, Mirror);

View File

@ -919,6 +919,48 @@ RUNTIME_FUNCTION(Runtime_GetFunctionScopeDetails) {
RETURN_RESULT_OR_FAILURE(isolate, it.MaterializeScopeDetails());
}
RUNTIME_FUNCTION(Runtime_GetGeneratorScopeCount) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
if (!args[0]->IsJSGeneratorObject()) return Smi::FromInt(0);
// Check arguments.
CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0);
// Count the visible scopes.
int n = 0;
for (ScopeIterator it(isolate, gen); !it.Done(); it.Next()) {
n++;
}
return Smi::FromInt(n);
}
RUNTIME_FUNCTION(Runtime_GetGeneratorScopeDetails) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
if (!args[0]->IsJSGeneratorObject()) {
return *isolate->factory()->undefined_value();
}
// Check arguments.
CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0);
CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
// Find the requested scope.
int n = 0;
ScopeIterator it(isolate, gen);
for (; !it.Done() && n < index; it.Next()) {
n++;
}
if (it.Done()) {
return isolate->heap()->undefined_value();
}
RETURN_RESULT_OR_FAILURE(isolate, it.MaterializeScopeDetails());
}
static bool SetScopeVariableValue(ScopeIterator* it, int index,
Handle<String> variable_name,
@ -967,10 +1009,14 @@ RUNTIME_FUNCTION(Runtime_SetScopeVariableValue) {
ScopeIterator it(isolate, &frame_inspector);
res = SetScopeVariableValue(&it, index, variable_name, new_value);
} else {
} else if (args[0]->IsJSFunction()) {
CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
ScopeIterator it(isolate, fun);
res = SetScopeVariableValue(&it, index, variable_name, new_value);
} else {
CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, gen, 0);
ScopeIterator it(isolate, gen);
res = SetScopeVariableValue(&it, index, variable_name, new_value);
}
return isolate->heap()->ToBoolean(res);

View File

@ -156,6 +156,8 @@ namespace internal {
F(GetAllScopesDetails, 4, 1) \
F(GetFunctionScopeCount, 1, 1) \
F(GetFunctionScopeDetails, 2, 1) \
F(GetGeneratorScopeCount, 1, 1) \
F(GetGeneratorScopeDetails, 2, 1) \
F(SetScopeVariableValue, 6, 1) \
F(DebugPrintScopes, 0, 1) \
F(SetBreakPointsActive, 1, 1) \

View File

@ -0,0 +1,470 @@
// 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: --expose-debug-as debug --allow-natives-syntax --ignition
// The functions used for testing backtraces. They are at the top to make the
// testing of source line/column easier.
// Get the Debug object exposed from the debug context global object.
var Debug = debug.Debug;
var test_name;
var exception;
var begin_test_count = 0;
var end_test_count = 0;
// Initialize for a new test.
function BeginTest(name) {
test_name = name;
exception = null;
begin_test_count++;
}
// Check result of a test.
function EndTest() {
assertNull(exception, test_name + " / " + exception);
end_test_count++;
}
// Check that two scope are the same.
function assertScopeMirrorEquals(scope1, scope2) {
assertEquals(scope1.scopeType(), scope2.scopeType());
assertEquals(scope1.frameIndex(), scope2.frameIndex());
assertEquals(scope1.scopeIndex(), scope2.scopeIndex());
assertPropertiesEqual(scope1.scopeObject().value(),
scope2.scopeObject().value());
}
// Check that the scope chain contains the expected types of scopes.
function CheckScopeChain(scopes, gen_mirror) {
var all_scopes = gen_mirror.allScopes();
assertEquals(scopes.length, gen_mirror.scopeCount());
assertEquals(scopes.length, all_scopes.length,
"FrameMirror.allScopes length");
for (var i = 0; i < scopes.length; i++) {
var scope = gen_mirror.scope(i);
assertTrue(scope.isScope());
assertEquals(scopes[i], scope.scopeType(),
`Scope ${i} has unexpected type`);
assertScopeMirrorEquals(all_scopes[i], scope);
// Check the global object when hitting the global scope.
if (scopes[i] == debug.ScopeType.Global) {
// Objects don't have same class (one is "global", other is "Object",
// so just check the properties directly.
assertPropertiesEqual(this, scope.scopeObject().value());
}
}
}
// Check that the content of the scope is as expected. For functions just check
// that there is a function.
function CheckScopeContent(content, number, gen_mirror) {
var scope = gen_mirror.scope(number);
var count = 0;
for (var p in content) {
var property_mirror = scope.scopeObject().property(p);
assertFalse(property_mirror.isUndefined(),
'property ' + p + ' not found in scope');
if (typeof(content[p]) === 'function') {
assertTrue(property_mirror.value().isFunction());
} else {
assertEquals(content[p], property_mirror.value().value(),
'property ' + p + ' has unexpected value');
}
count++;
}
// 'arguments' and might be exposed in the local and closure scope. Just
// ignore this.
var scope_size = scope.scopeObject().properties().length;
if (!scope.scopeObject().property('arguments').isUndefined()) {
scope_size--;
}
// Ditto for 'this'.
if (!scope.scopeObject().property('this').isUndefined()) {
scope_size--;
}
// Temporary variables introduced by the parser have not been materialized.
assertTrue(scope.scopeObject().property('').isUndefined());
if (count != scope_size) {
print('Names found in scope:');
var names = scope.scopeObject().propertyNames();
for (var i = 0; i < names.length; i++) {
print(names[i]);
}
}
assertEquals(count, scope_size);
}
// Simple empty closure scope.
function *gen1() {
yield 1;
return 2;
}
var g = gen1();
var gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({}, 0, gm);
// Closure scope with a parameter.
function *gen2(a) {
yield a;
return 2;
}
g = gen2(42);
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 42}, 0, gm);
// Closure scope with a parameter.
function *gen3(a) {
var b = 1
yield a;
return b;
}
g = gen3(0);
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 0, b: undefined}, 0, gm);
g.next(); // Create b.
CheckScopeContent({a: 0, b: 1}, 0, gm);
// Closure scope with a parameter.
function *gen4(a, b) {
var x = 2;
yield a;
var y = 3;
return b;
}
g = gen4(0, 1);
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 0, b: 1, x: undefined, y: undefined}, 0, gm);
g.next(); // Create x.
CheckScopeContent({a: 0, b: 1, x: 2, y: undefined}, 0, gm);
g.next(); // Create y.
CheckScopeContent({a: 0, b: 1, x: 2, y: 3}, 0, gm);
// Closure introducing local variable using eval.
function *gen5(a) {
eval('var b = 2');
return b;
}
g = gen5(1);
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 1, b: 2}, 0, gm);
// Single empty with block.
function *gen6() {
with({}) {
yield 1;
}
yield 2;
return 3;
}
g = gen6();
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.With,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({}, 0, gm);
g.next();
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
// Nested empty with blocks.
function *gen7() {
with({}) {
with({}) {
yield 1;
}
yield 2;
}
return 3;
}
g = gen7();
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.With,
debug.ScopeType.With,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({}, 0, gm);
// Nested with blocks using in-place object literals.
function *gen8() {
with({a: 1,b: 2}) {
with({a: 2,b: 1}) {
yield a;
}
yield a;
}
return 3;
}
g = gen8();
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.With,
debug.ScopeType.With,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 2, b: 1}, 0, gm);
g.next();
CheckScopeContent({a: 1, b: 2}, 0, gm);
// Catch block.
function *gen9() {
try {
throw 42;
} catch (e) {
yield e;
}
return 3;
}
g = gen9();
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Catch,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({e: 42}, 0, gm);
// For statement with block scope.
function *gen10() {
for (let i = 0; i < 42; i++) yield i;
return 3;
}
g = gen10();
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Block,
debug.ScopeType.Block,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({i: 0}, 0, gm);
g.next();
CheckScopeContent({i: 1}, 0, gm);
CheckScopeContent({i: 0}, 1, gm); // Additional block scope with i = 0;
// Nested generators.
var gen12;
function *gen11() {
gen12 = function*() {
var a = 1;
yield 1;
return 2;
}();
var a = 0;
yield* gen12;
}
g = gen11();
g.next();
gm = debug.MakeMirror(gen12);
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 1}, 0, gm);
CheckScopeContent({a: 0}, 1, gm);
// Set a variable in an empty scope.
function *gen13() {
yield 1;
return 2;
}
var g = gen13();
var gm = debug.MakeMirror(g);
assertThrows(() => gm.scope(0).setVariableValue("a", 42));
CheckScopeContent({}, 0, gm);
// Set a variable in a simple scope.
function *gen14() {
var a = 0;
yield 1;
yield a;
return 2;
}
var g = gen14();
assertEquals(1, g.next().value);
var gm = debug.MakeMirror(g);
CheckScopeContent({a: 0}, 0, gm);
gm.scope(0).setVariableValue("a", 1);
CheckScopeContent({a: 1}, 0, gm);
assertEquals(1, g.next().value);
// Set a variable in nested with blocks using in-place object literals.
function *gen15() {
var c = 3;
with({a: 1,b: 2}) {
var d = 4;
yield a;
var e = 5;
}
yield e;
return e;
}
var g = gen15();
assertEquals(1, g.next().value);
var gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.With,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({a: 1, b: 2}, 0, gm);
CheckScopeContent({c: 3, d: 4, e: undefined}, 1, gm);
// Variables don't exist in given scope.
assertThrows(() => gm.scope(0).setVariableValue("c", 42));
assertThrows(() => gm.scope(1).setVariableValue("a", 42));
// Variables in with scope are immutable.
assertThrows(() => gm.scope(0).setVariableValue("a", 3));
assertThrows(() => gm.scope(0).setVariableValue("b", 3));
gm.scope(1).setVariableValue("c", 1);
gm.scope(1).setVariableValue("e", 42);
CheckScopeContent({a: 1, b: 2}, 0, gm);
CheckScopeContent({c: 1, d: 4, e: 42}, 1, gm);
assertEquals(5, g.next().value); // Initialized after set.
CheckScopeChain([debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
gm.scope(0).setVariableValue("e", 42);
CheckScopeContent({c: 1, d: 4, e: 42}, 0, gm);
assertEquals(42, g.next().value);
// Set a variable in nested with blocks using in-place object literals plus a
// nested block scope.
function *gen16() {
var c = 3;
with({a: 1,b: 2}) {
let d = 4;
yield a;
let e = 5;
yield d;
}
return 3;
}
var g = gen16();
g.next();
var gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Block,
debug.ScopeType.With,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({d: 4}, 0, gm);
CheckScopeContent({a: 1, b: 2}, 1, gm);
CheckScopeContent({c: 3}, 2, gm);
gm.scope(0).setVariableValue("d", 1);
CheckScopeContent({d: 1}, 0, gm);
assertEquals(1, g.next().value);
// Set variable in catch block.
var yyzyzzyz = 4829;
let xxxyyxxyx = 42284;
function *gen17() {
try {
throw 42;
} catch (e) {
yield e;
yield e;
}
return 3;
}
g = gen17();
g.next();
gm = debug.MakeMirror(g);
CheckScopeChain([debug.ScopeType.Catch,
debug.ScopeType.Closure,
debug.ScopeType.Script,
debug.ScopeType.Global], gm);
CheckScopeContent({e: 42}, 0, gm);
CheckScopeContent({xxxyyxxyx: 42284}, 2, gm);
gm.scope(0).setVariableValue("e", 1);
CheckScopeContent({e: 1}, 0, gm);
assertEquals(1, g.next().value);
// Script scope.
gm.scope(2).setVariableValue("xxxyyxxyx", 42);
assertEquals(42, xxxyyxxyx);
// Global scope.
assertThrows(() => gm.scope(3).setVariableValue("yyzyzzyz", 42));
assertEquals(4829, yyzyzzyz);