From 0dab442be9ddc125b63419e00c1cedb2bef874dd Mon Sep 17 00:00:00 2001 From: "sgjesse@chromium.org" Date: Thu, 22 Jan 2009 13:20:31 +0000 Subject: [PATCH] Added handling of hidden prototype objects when collecting local properties for an object mirror. The property names provided by an object mirror now includes all properties from the object and any hidden prototypes merged together. Changed the name of Runtime_GetPrototype to Runtime_DebugGetPrototype to indicate that it is a debugger related function and changed its implementation to do the correct __proto__ lookup. Added some more information to the Map debug print. Review URL: http://codereview.chromium.org/18658 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1126 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/mirror-delay.js | 2 +- src/objects-debug.cc | 18 +++++++++ src/objects.cc | 10 ++--- src/objects.h | 5 ++- src/runtime.cc | 83 +++++++++++++++++++++++++++++++++++++---- src/runtime.h | 2 +- test/cctest/test-debug.cc | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 199 insertions(+), 16 deletions(-) diff --git a/src/mirror-delay.js b/src/mirror-delay.js index 4b2cacb..6cf9d4f 100644 --- a/src/mirror-delay.js +++ b/src/mirror-delay.js @@ -525,7 +525,7 @@ ObjectMirror.prototype.prototypeObject = function() { ObjectMirror.prototype.protoObject = function() { - return MakeMirror(%GetPrototype(this.value_)); + return MakeMirror(%DebugGetPrototype(this.value_)); }; diff --git a/src/objects-debug.cc b/src/objects-debug.cc index 0d54195..d3ac805 100644 --- a/src/objects-debug.cc +++ b/src/objects-debug.cc @@ -414,6 +414,24 @@ void Map::MapPrint() { PrintF(" - type: %s\n", TypeToString(instance_type())); PrintF(" - instance size: %d\n", instance_size()); PrintF(" - unused property fields: %d\n", unused_property_fields()); + if (is_hidden_prototype()) { + PrintF(" - hidden_prototype\n"); + } + if (has_named_interceptor()) { + PrintF(" - named_interceptor\n"); + } + if (has_indexed_interceptor()) { + PrintF(" - indexed_interceptor\n"); + } + if (is_undetectable()) { + PrintF(" - undetectable\n"); + } + if (has_instance_call_handler()) { + PrintF(" - instance_call_handler\n"); + } + if (is_access_check_needed()) { + PrintF(" - access_check_needed\n"); + } PrintF(" - instance descriptors: "); instance_descriptors()->ShortPrint(); PrintF("\n - prototype: "); diff --git a/src/objects.cc b/src/objects.cc index 352f5bd..aa51458 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -5695,10 +5695,10 @@ void FixedArray::SortPairs(FixedArray* smis) { // Fill in the names of local properties into the supplied storage. The main // purpose of this function is to provide reflection information for the object // mirrors. -void JSObject::GetLocalPropertyNames(FixedArray* storage) { - ASSERT(storage->length() == - NumberOfLocalProperties(static_cast(NONE))); - int index = 0; +void JSObject::GetLocalPropertyNames(FixedArray* storage, int index) { + ASSERT(storage->length() >= + NumberOfLocalProperties(static_cast(NONE)) - + index); if (HasFastProperties()) { for (DescriptorReader r(map()->instance_descriptors()); !r.eos(); @@ -5707,7 +5707,7 @@ void JSObject::GetLocalPropertyNames(FixedArray* storage) { storage->set(index++, r.GetKey()); } } - ASSERT(storage->length() == index); + ASSERT(storage->length() >= index); } else { property_dictionary()->CopyKeysTo(storage); } diff --git a/src/objects.h b/src/objects.h index b77c0b2..cb1bee8 100644 --- a/src/objects.h +++ b/src/objects.h @@ -1298,8 +1298,9 @@ class JSObject: public HeapObject { int NumberOfLocalProperties(PropertyAttributes filter); // Returns the number of enumerable properties (ignoring interceptors). int NumberOfEnumProperties(); - // Fill in details for properties into storage. - void GetLocalPropertyNames(FixedArray* storage); + // Fill in details for properties into storage starting at the specified + // index. + void GetLocalPropertyNames(FixedArray* storage, int index); // Returns the number of properties on this object filtering out properties // with the specified attributes (ignoring interceptors). diff --git a/src/runtime.cc b/src/runtime.cc index 2dc5719..b53b9cd 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -4536,6 +4536,21 @@ static Object* Runtime_Break(Arguments args) { } +// Find the length of the prototype chain that is to to handled as one. If a +// prototype object is hidden it is to be viewed as part of the the object it +// is prototype for. +static int LocalPrototypeChainLength(JSObject* obj) { + int count = 1; + Object* proto = obj->GetPrototype(); + while (proto->IsJSObject() && + JSObject::cast(proto)->map()->is_hidden_prototype()) { + count++; + proto = JSObject::cast(proto)->GetPrototype(); + } + return count; +} + + static Object* DebugLookupResultValue(Object* receiver, LookupResult* result, bool* caught_exception) { Object* value; @@ -4611,6 +4626,13 @@ static Object* Runtime_DebugGetPropertyDetails(Arguments args) { CONVERT_ARG_CHECKED(JSObject, obj, 0); CONVERT_ARG_CHECKED(String, name, 1); + // Skip the global proxy as it has no properties and always delegates to the + // real global object. + if (obj->IsJSGlobalProxy()) { + obj = Handle(JSObject::cast(obj->GetPrototype())); + } + + // Check if the name is trivially convertible to an index and get the element // if so. uint32_t index; @@ -4621,9 +4643,22 @@ static Object* Runtime_DebugGetPropertyDetails(Arguments args) { return *Factory::NewJSArrayWithElements(details); } - // Perform standard local lookup on the object. + // Find the number of objects making up this. + int length = LocalPrototypeChainLength(*obj); + + // Try local lookup on each of the objects. LookupResult result; - obj->LocalLookup(*name, &result); + Handle jsproto = obj; + for (int i = 0; i < length; i++) { + jsproto->LocalLookup(*name, &result); + if (result.IsProperty()) { + break; + } + if (i < length - 1) { + jsproto = Handle(JSObject::cast(jsproto->GetPrototype())); + } + } + if (result.IsProperty()) { bool caught_exception = false; Handle value(DebugLookupResultValue(*obj, &result, @@ -4676,12 +4711,43 @@ static Object* Runtime_DebugLocalPropertyNames(Arguments args) { } CONVERT_ARG_CHECKED(JSObject, obj, 0); + // Skip the global proxy as it has no properties and always delegates to the + // real global object. if (obj->IsJSGlobalProxy()) { obj = Handle(JSObject::cast(obj->GetPrototype())); } - int n = obj->NumberOfLocalProperties(static_cast(NONE)); - Handle names = Factory::NewFixedArray(n); - obj->GetLocalPropertyNames(*names); + + // Find the number of objects making up this. + int length = LocalPrototypeChainLength(*obj); + + // Find the number of local properties for each of the objects. + int* local_property_count = NewArray(length); + int total_property_count = 0; + Handle jsproto = obj; + for (int i = 0; i < length; i++) { + int n; + n = jsproto->NumberOfLocalProperties(static_cast(NONE)); + local_property_count[i] = n; + total_property_count += n; + if (i < length - 1) { + jsproto = Handle(JSObject::cast(jsproto->GetPrototype())); + } + } + + // Allocate an array with storage for all the property names. + Handle names = Factory::NewFixedArray(total_property_count); + + // Get the property names. + jsproto = obj; + for (int i = 0; i < length; i++) { + jsproto->GetLocalPropertyNames(*names, + i == 0 ? 0 : local_property_count[i - 1]); + if (i < length - 1) { + jsproto = Handle(JSObject::cast(jsproto->GetPrototype())); + } + } + + DeleteArray(local_property_count); return *Factory::NewJSArrayWithElements(names); } @@ -5809,12 +5875,15 @@ static Object* Runtime_DebugConstructedBy(Arguments args) { } -static Object* Runtime_GetPrototype(Arguments args) { +// Find the effective prototype object as returned by __proto__. +// args[0]: the object to find the prototype for. +static Object* Runtime_DebugGetPrototype(Arguments args) { ASSERT(args.length() == 1); CONVERT_CHECKED(JSObject, obj, args[0]); - return obj->GetPrototype(); + // Use the __proto__ accessor. + return Accessors::ObjectPrototype.getter(obj, NULL); } diff --git a/src/runtime.h b/src/runtime.h index ebe70a4..1924317 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -244,7 +244,7 @@ namespace v8 { namespace internal { F(DebugGetLoadedScripts, 0) \ F(DebugReferencedBy, 3) \ F(DebugConstructedBy, 2) \ - F(GetPrototype, 1) \ + F(DebugGetPrototype, 1) \ F(SystemBreak, 0) \ \ /* Literals */ \ diff --git a/test/cctest/test-debug.cc b/test/cctest/test-debug.cc index cd85b8f..15ebf16 100644 --- a/test/cctest/test-debug.cc +++ b/test/cctest/test-debug.cc @@ -2749,6 +2749,101 @@ TEST(InterceptorPropertyMirror) { } +TEST(HiddenPrototypePropertyMirror) { + // Create a V8 environment with debug access. + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Handle t0 = v8::FunctionTemplate::New(); + t0->InstanceTemplate()->Set(v8::String::New("x"), v8::Number::New(0)); + v8::Handle t1 = v8::FunctionTemplate::New(); + t1->SetHiddenPrototype(true); + t1->InstanceTemplate()->Set(v8::String::New("y"), v8::Number::New(1)); + v8::Handle t2 = v8::FunctionTemplate::New(); + t2->SetHiddenPrototype(true); + t2->InstanceTemplate()->Set(v8::String::New("z"), v8::Number::New(2)); + v8::Handle t3 = v8::FunctionTemplate::New(); + t3->InstanceTemplate()->Set(v8::String::New("u"), v8::Number::New(3)); + + // Create object and set them on the global object. + v8::Handle o0 = t0->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o0"), o0); + v8::Handle o1 = t1->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o1"), o1); + v8::Handle o2 = t2->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o2"), o2); + v8::Handle o3 = t3->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o3"), o3); + + // Get mirrors for the four objects. + CompileRun( + "o0_mirror = debug.MakeMirror(o0);" + "o1_mirror = debug.MakeMirror(o1);" + "o2_mirror = debug.MakeMirror(o2);" + "o3_mirror = debug.MakeMirror(o3)"); + CHECK(CompileRun("o0_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun("o1_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun("o2_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun("o3_mirror instanceof debug.ObjectMirror")->BooleanValue()); + + // Check that each object has one property. + CHECK_EQ(1, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o1_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o2_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o3_mirror.propertyNames().length")->Int32Value()); + + // Set o1 as prototype for o0. o1 has the hidden prototype flag so all + // properties on o1 should be seen on o0. + o0->Set(v8::String::New("__proto__"), o1); + CHECK_EQ(2, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(0, CompileRun( + "o0_mirror.property('x').value().value()")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o0_mirror.property('y').value().value()")->Int32Value()); + + // Set o2 as prototype for o0 (it will end up after o1 as o1 has the hidden + // prototype flag. o2 also has the hidden prototype flag so all properties + // on o2 should be seen on o0 as well as properties on o1. + o0->Set(v8::String::New("__proto__"), o2); + CHECK_EQ(3, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(0, CompileRun( + "o0_mirror.property('x').value().value()")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o0_mirror.property('y').value().value()")->Int32Value()); + CHECK_EQ(2, CompileRun( + "o0_mirror.property('z').value().value()")->Int32Value()); + + // Set o3 as prototype for o0 (it will end up after o1 and o2 as both o1 and + // o2 has the hidden prototype flag. o3 does not have the hidden prototype + // flag so properties on o3 should not be seen on o0 whereas the properties + // from o1 and o2 should still be seen on o0. + // Final prototype chain: o0 -> o1 -> o2 -> o3 + // Hidden prototypes: ^^ ^^ + o0->Set(v8::String::New("__proto__"), o3); + CHECK_EQ(3, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o3_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(0, CompileRun( + "o0_mirror.property('x').value().value()")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o0_mirror.property('y').value().value()")->Int32Value()); + CHECK_EQ(2, CompileRun( + "o0_mirror.property('z').value().value()")->Int32Value()); + CHECK(CompileRun("o0_mirror.property('u').isUndefined()")->BooleanValue()); + + // The prototype (__proto__) for o0 should be o3 as o1 and o2 are hidden. + CHECK(CompileRun("o0_mirror.protoObject() == o3_mirror")->BooleanValue()); +} + + // Multithreaded tests of JSON debugger protocol // Support classes -- 2.7.4