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:
parent
03ef40b46c
commit
ae6ec1861e
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
23
src/api.cc
23
src/api.cc
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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>);
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user