Provide accessor for object internal properties that doesn't require debugger to be active

Some of the DevTools' clients need to inspect JS objects without enabling debugger. This CL allows to inspect object's internal properties without enabling debugger and instantiating debug context.

Note that now debug context can be created lazily if v8::Debug::GetDebugContext is called when there is no debug listener. This is fragile and has already resulted in some subtle error. I'm going to fix that in a separate CL.

BUG=chromium:481845
LOG=Y

Review URL: https://codereview.chromium.org/1134193002

Cr-Commit-Position: refs/heads/master@{#28371}
This commit is contained in:
yurys 2015-05-12 08:40:31 -07:00 committed by Commit bot
parent 03ef40b46c
commit ae6ec1861e
9 changed files with 225 additions and 60 deletions

View File

@ -260,6 +260,14 @@ class V8_EXPORT Debug {
* unexpectedly used. LiveEdit is enabled by default.
*/
static void SetLiveEditEnabled(Isolate* isolate, bool enable);
/**
* Returns array of internal properties specific to the value type. Result has
* the following format: [<name>, <value>,...,<name>, <value>]. Result array
* will be allocated in the current context.
*/
static MaybeLocal<Array> GetInternalProperties(Isolate* isolate,
Local<Value> value);
};

View File

@ -6090,16 +6090,7 @@ Local<Object> Array::CloneElementAt(uint32_t index) {
bool Value::IsPromise() const {
auto self = Utils::OpenHandle(this);
if (!self->IsJSObject()) return false;
auto js_object = i::Handle<i::JSObject>::cast(self);
// Promises can't have access checks.
if (js_object->map()->is_access_check_needed()) return false;
auto isolate = js_object->GetIsolate();
// TODO(dcarney): this should just be read from the symbol registry so as not
// to be context dependent.
auto key = isolate->promise_status();
// Shouldn't be possible to throw here.
return i::JSObject::HasRealNamedProperty(js_object, key).FromJust();
return i::Object::IsPromise(self);
}
@ -7394,6 +7385,18 @@ void Debug::SetLiveEditEnabled(Isolate* isolate, bool enable) {
}
MaybeLocal<Array> Debug::GetInternalProperties(Isolate* v8_isolate,
Local<Value> value) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8(isolate);
i::Handle<i::Object> val = Utils::OpenHandle(*value);
i::Handle<i::JSArray> result;
if (!i::Runtime::GetInternalProperties(isolate, val).ToHandle(&result))
return MaybeLocal<Array>();
return Utils::ToLocal(result);
}
Handle<String> CpuProfileNode::GetFunctionName() const {
i::Isolate* isolate = i::Isolate::Current();
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);

View File

@ -1612,6 +1612,7 @@ void Genesis::InstallNativeFunctions() {
to_complete_property_descriptor);
INSTALL_NATIVE(Symbol, "$promiseStatus", promise_status);
INSTALL_NATIVE(Symbol, "$promiseValue", promise_value);
INSTALL_NATIVE(JSFunction, "$promiseCreate", promise_create);
INSTALL_NATIVE(JSFunction, "$promiseResolve", promise_resolve);
INSTALL_NATIVE(JSFunction, "$promiseReject", promise_reject);

View File

@ -157,6 +157,7 @@ enum BindingFlags {
V(ERROR_MESSAGE_FOR_CODE_GEN_FROM_STRINGS_INDEX, Object, \
error_message_for_code_gen_from_strings) \
V(PROMISE_STATUS_INDEX, Symbol, promise_status) \
V(PROMISE_VALUE_INDEX, Symbol, promise_value) \
V(PROMISE_CREATE_INDEX, JSFunction, promise_create) \
V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \
V(PROMISE_REJECT_INDEX, JSFunction, promise_reject) \
@ -394,6 +395,7 @@ class Context: public FixedArray {
RUN_MICROTASKS_INDEX,
ENQUEUE_MICROTASK_INDEX,
PROMISE_STATUS_INDEX,
PROMISE_VALUE_INDEX,
PROMISE_CREATE_INDEX,
PROMISE_RESOLVE_INDEX,
PROMISE_REJECT_INDEX,

View File

@ -904,57 +904,12 @@ ObjectMirror.prototype.toText = function() {
* @return {Array} array (possibly empty) of InternalProperty instances
*/
ObjectMirror.GetInternalProperties = function(value) {
if (IS_STRING_WRAPPER(value) || IS_NUMBER_WRAPPER(value) ||
IS_BOOLEAN_WRAPPER(value)) {
var primitiveValue = %_ValueOf(value);
return [new InternalPropertyMirror("[[PrimitiveValue]]", primitiveValue)];
} else if (IS_FUNCTION(value)) {
var bindings = %BoundFunctionGetBindings(value);
var result = [];
if (bindings && IS_ARRAY(bindings)) {
result.push(new InternalPropertyMirror("[[TargetFunction]]",
bindings[0]));
result.push(new InternalPropertyMirror("[[BoundThis]]", bindings[1]));
var boundArgs = [];
for (var i = 2; i < bindings.length; i++) {
boundArgs.push(bindings[i]);
}
result.push(new InternalPropertyMirror("[[BoundArgs]]", boundArgs));
}
return result;
} else if (IS_MAP_ITERATOR(value) || IS_SET_ITERATOR(value)) {
var details = IS_MAP_ITERATOR(value) ? %MapIteratorDetails(value)
: %SetIteratorDetails(value);
var kind;
switch (details[2]) {
case 1: kind = "keys"; break;
case 2: kind = "values"; break;
case 3: kind = "entries"; break;
}
var result = [
new InternalPropertyMirror("[[IteratorHasMore]]", details[0]),
new InternalPropertyMirror("[[IteratorIndex]]", details[1])
];
if (kind) {
result.push(new InternalPropertyMirror("[[IteratorKind]]", kind));
}
return result;
} else if (IS_GENERATOR(value)) {
return [
new InternalPropertyMirror("[[GeneratorStatus]]",
GeneratorGetStatus_(value)),
new InternalPropertyMirror("[[GeneratorFunction]]",
%GeneratorGetFunction(value)),
new InternalPropertyMirror("[[GeneratorReceiver]]",
%GeneratorGetReceiver(value))
];
} else if (ObjectIsPromise(value)) {
return [
new InternalPropertyMirror("[[PromiseStatus]]", PromiseGetStatus_(value)),
new InternalPropertyMirror("[[PromiseValue]]", PromiseGetValue_(value))
];
var properties = %DebugGetInternalProperties(value);
var result = [];
for (var i = 0; i < properties.length; i += 2) {
result.push(new InternalPropertyMirror(properties[i], properties[i + 1]));
}
return [];
return result;
}

View File

@ -112,6 +112,20 @@ bool Object::IsCallable() const {
}
bool Object::IsPromise(Handle<Object> object) {
if (!object->IsJSObject()) return false;
auto js_object = Handle<JSObject>::cast(object);
// Promises can't have access checks.
if (js_object->map()->is_access_check_needed()) return false;
auto isolate = js_object->GetIsolate();
// TODO(dcarney): this should just be read from the symbol registry so as not
// to be context dependent.
auto key = isolate->promise_status();
// Shouldn't be possible to throw here.
return JSObject::HasRealNamedProperty(js_object, key).FromJust();
}
MaybeHandle<Object> Object::GetProperty(LookupIterator* it) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {

View File

@ -1056,6 +1056,7 @@ class Object {
INLINE(bool IsOrderedHashSet() const);
INLINE(bool IsOrderedHashMap() const);
bool IsCallable() const;
static bool IsPromise(Handle<Object> object);
// Oddball testing.
INLINE(bool IsUndefined() const);

View File

@ -101,6 +101,183 @@ static Handle<Object> DebugGetProperty(LookupIterator* it,
}
static Handle<Object> DebugGetProperty(Handle<Object> object,
Handle<Name> name) {
LookupIterator it(object, name);
return DebugGetProperty(&it);
}
template <class IteratorType>
static MaybeHandle<JSArray> GetIteratorInternalProperties(
Isolate* isolate, Handle<IteratorType> object) {
Factory* factory = isolate->factory();
Handle<IteratorType> iterator = Handle<IteratorType>::cast(object);
RUNTIME_ASSERT_HANDLIFIED(iterator->kind()->IsSmi(), JSArray);
const char* kind = NULL;
switch (Smi::cast(iterator->kind())->value()) {
case IteratorType::kKindKeys:
kind = "keys";
break;
case IteratorType::kKindValues:
kind = "values";
break;
case IteratorType::kKindEntries:
kind = "entries";
break;
default:
RUNTIME_ASSERT_HANDLIFIED(false, JSArray);
}
Handle<FixedArray> result = factory->NewFixedArray(2 * 3);
Handle<String> has_more =
factory->NewStringFromAsciiChecked("[[IteratorHasMore]]");
result->set(0, *has_more);
result->set(1, isolate->heap()->ToBoolean(iterator->HasMore()));
Handle<String> index =
factory->NewStringFromAsciiChecked("[[IteratorIndex]]");
result->set(2, *index);
result->set(3, iterator->index());
Handle<String> iterator_kind =
factory->NewStringFromAsciiChecked("[[IteratorKind]]");
result->set(4, *iterator_kind);
Handle<String> kind_str = factory->NewStringFromAsciiChecked(kind);
result->set(5, *kind_str);
return factory->NewJSArrayWithElements(result);
}
MaybeHandle<JSArray> Runtime::GetInternalProperties(Isolate* isolate,
Handle<Object> object) {
Factory* factory = isolate->factory();
if (object->IsJSFunction()) {
Handle<JSFunction> function = Handle<JSFunction>::cast(object);
if (function->shared()->bound()) {
RUNTIME_ASSERT_HANDLIFIED(function->function_bindings()->IsFixedArray(),
JSArray);
Handle<FixedArray> bindings(function->function_bindings());
Handle<FixedArray> result = factory->NewFixedArray(2 * 3);
Handle<String> target =
factory->NewStringFromAsciiChecked("[[TargetFunction]]");
result->set(0, *target);
result->set(1, bindings->get(JSFunction::kBoundFunctionIndex));
Handle<String> bound_this =
factory->NewStringFromAsciiChecked("[[BoundThis]]");
result->set(2, *bound_this);
result->set(3, bindings->get(JSFunction::kBoundThisIndex));
Handle<FixedArray> arguments = factory->NewFixedArray(
bindings->length() - JSFunction::kBoundArgumentsStartIndex);
bindings->CopyTo(
JSFunction::kBoundArgumentsStartIndex, *arguments, 0,
bindings->length() - JSFunction::kBoundArgumentsStartIndex);
Handle<String> bound_args =
factory->NewStringFromAsciiChecked("[[BoundArgs]]");
result->set(4, *bound_args);
Handle<JSArray> arguments_array =
factory->NewJSArrayWithElements(arguments);
result->set(5, *arguments_array);
return factory->NewJSArrayWithElements(result);
}
} else if (object->IsJSMapIterator()) {
Handle<JSMapIterator> iterator = Handle<JSMapIterator>::cast(object);
return GetIteratorInternalProperties(isolate, iterator);
} else if (object->IsJSSetIterator()) {
Handle<JSSetIterator> iterator = Handle<JSSetIterator>::cast(object);
return GetIteratorInternalProperties(isolate, iterator);
} else if (object->IsJSGeneratorObject()) {
Handle<JSGeneratorObject> generator =
Handle<JSGeneratorObject>::cast(object);
const char* status = "suspended";
if (generator->is_closed()) {
status = "closed";
} else if (generator->is_executing()) {
status = "running";
} else {
DCHECK(generator->is_suspended());
}
Handle<FixedArray> result = factory->NewFixedArray(2 * 3);
Handle<String> generator_status =
factory->NewStringFromAsciiChecked("[[GeneratorStatus]]");
result->set(0, *generator_status);
Handle<String> status_str = factory->NewStringFromAsciiChecked(status);
result->set(1, *status_str);
Handle<String> function =
factory->NewStringFromAsciiChecked("[[GeneratorFunction]]");
result->set(2, *function);
result->set(3, generator->function());
Handle<String> receiver =
factory->NewStringFromAsciiChecked("[[GeneratorReceiver]]");
result->set(4, *receiver);
result->set(5, generator->receiver());
return factory->NewJSArrayWithElements(result);
} else if (Object::IsPromise(object)) {
Handle<JSObject> promise = Handle<JSObject>::cast(object);
Handle<Object> status_obj =
DebugGetProperty(promise, isolate->promise_status());
RUNTIME_ASSERT_HANDLIFIED(status_obj->IsSmi(), JSArray);
const char* status = "rejected";
int status_val = Handle<Smi>::cast(status_obj)->value();
switch (status_val) {
case +1:
status = "resolved";
break;
case 0:
status = "pending";
break;
default:
DCHECK_EQ(-1, status_val);
}
Handle<FixedArray> result = factory->NewFixedArray(2 * 2);
Handle<String> promise_status =
factory->NewStringFromAsciiChecked("[[PromiseStatus]]");
result->set(0, *promise_status);
Handle<String> status_str = factory->NewStringFromAsciiChecked(status);
result->set(1, *status_str);
Handle<Object> value_obj =
DebugGetProperty(promise, isolate->promise_value());
Handle<String> promise_value =
factory->NewStringFromAsciiChecked("[[PromiseValue]]");
result->set(2, *promise_value);
result->set(3, *value_obj);
return factory->NewJSArrayWithElements(result);
} else if (object->IsJSValue()) {
Handle<JSValue> js_value = Handle<JSValue>::cast(object);
Handle<FixedArray> result = factory->NewFixedArray(2);
Handle<String> primitive_value =
factory->NewStringFromAsciiChecked("[[PrimitiveValue]]");
result->set(0, *primitive_value);
result->set(1, js_value->value());
return factory->NewJSArrayWithElements(result);
}
return factory->NewJSArray(0);
}
RUNTIME_FUNCTION(Runtime_DebugGetInternalProperties) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(Object, obj, 0);
Handle<JSArray> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, Runtime::GetInternalProperties(isolate, obj));
return *result;
}
// Get debugger related details for an object property, in the following format:
// 0: Property value
// 1: Property details

View File

@ -134,6 +134,7 @@ namespace internal {
F(DebugBreak, 0, 1) \
F(SetDebugEventListener, 2, 1) \
F(ScheduleBreak, 0, 1) \
F(DebugGetInternalProperties, 1, 1) \
F(DebugGetPropertyDetails, 2, 1) \
F(DebugGetProperty, 2, 1) \
F(DebugPropertyTypeFromDetails, 1, 1) \
@ -854,6 +855,9 @@ class Runtime : public AllStatic {
Handle<Object> key, Handle<Object> value);
static bool WeakCollectionDelete(Handle<JSWeakCollection> weak_collection,
Handle<Object> key);
static MaybeHandle<JSArray> GetInternalProperties(Isolate* isolate,
Handle<Object>);
};