Commit b141fedf authored by Benedikt Meurer's avatar Benedikt Meurer Committed by V8 LUCI CQ

[inspector] Improve class name inference.

Previously having a `Symbol.toStringTag` property holding a string
somewhere in the prototype chain would always take predence over trying
to determine a reasonable name from the "constructor" (in case of
subclassing). This would lead to confusing behavior when diagnosing
objects in DevTools, for example

```js
class A extends URLSearchParams {};
new A()
```

would show `URLSearchParam` as class name for the instance rather than
`A`.

With this CL, we change the lookup logic to explicitly check for
`Symbol.toStringTag` and "constructor" along each step of the prototype
chain (skipping the "constructor" for the leaf object) and pick the
first one that yields a string (that is the function debug name in case
of "constructor").

Fixed: chromium:980018
Change-Id: Ic920b4bae02f965bc383c711f8de89c0de55fcac
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3231078
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Kim-Anh Tran <kimanh@chromium.org>
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarKim-Anh Tran <kimanh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77453}
parent 15261036
...@@ -500,36 +500,46 @@ std::pair<MaybeHandle<JSFunction>, Handle<String>> GetConstructorHelper( ...@@ -500,36 +500,46 @@ std::pair<MaybeHandle<JSFunction>, Handle<String>> GetConstructorHelper(
} }
} }
LookupIterator it_tag(isolate, receiver, for (PrototypeIterator it(isolate, receiver, kStartAtReceiver); !it.IsAtEnd();
isolate->factory()->to_string_tag_symbol(), receiver, it.AdvanceIgnoringProxies()) {
LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR); auto current = PrototypeIterator::GetCurrent<JSReceiver>(it);
Handle<Object> maybe_tag = JSReceiver::GetDataProperty(
&it_tag, AllocationPolicy::kAllocationDisallowed); LookupIterator it_to_string_tag(
if (maybe_tag->IsString()) { isolate, receiver, isolate->factory()->to_string_tag_symbol(), current,
return std::make_pair(MaybeHandle<JSFunction>(), LookupIterator::OWN_SKIP_INTERCEPTOR);
Handle<String>::cast(maybe_tag)); auto maybe_to_string_tag = JSReceiver::GetDataProperty(
} &it_to_string_tag, AllocationPolicy::kAllocationDisallowed);
if (maybe_to_string_tag->IsString()) {
PrototypeIterator iter(isolate, receiver); return std::make_pair(MaybeHandle<JSFunction>(),
if (iter.IsAtEnd()) { Handle<String>::cast(maybe_to_string_tag));
return std::make_pair(MaybeHandle<JSFunction>(), }
handle(receiver->class_name(), isolate));
} // Consider the following example:
//
Handle<JSReceiver> start = PrototypeIterator::GetCurrent<JSReceiver>(iter); // function A() {}
LookupIterator it(isolate, receiver, isolate->factory()->constructor_string(), // function B() {}
start, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR); // B.prototype = new A();
Handle<Object> maybe_constructor = // B.prototype.constructor = B;
JSReceiver::GetDataProperty(&it, AllocationPolicy::kAllocationDisallowed); //
if (maybe_constructor->IsJSFunction()) { // The constructor name for `B.prototype` must yield "A", so we don't take
Handle<JSFunction> constructor = // "constructor" into account for the receiver itself, but only starting
Handle<JSFunction>::cast(maybe_constructor); // on the prototype chain.
Handle<String> name = if (!receiver.is_identical_to(current)) {
SharedFunctionInfo::DebugName(handle(constructor->shared(), isolate)); LookupIterator it_constructor(
isolate, receiver, isolate->factory()->constructor_string(), current,
if (name->length() != 0 && LookupIterator::OWN_SKIP_INTERCEPTOR);
!name->Equals(ReadOnlyRoots(isolate).Object_string())) { auto maybe_constructor = JSReceiver::GetDataProperty(
return std::make_pair(constructor, name); &it_constructor, AllocationPolicy::kAllocationDisallowed);
if (maybe_constructor->IsJSFunction()) {
auto constructor = Handle<JSFunction>::cast(maybe_constructor);
auto name = SharedFunctionInfo::DebugName(
handle(constructor->shared(), isolate));
if (name->length() != 0 &&
!name->Equals(ReadOnlyRoots(isolate).Object_string())) {
return std::make_pair(constructor, name);
}
}
} }
} }
......
Regression test for crbug.com/980018
Running test: testClassNames
{
result : {
className : A
description : A(0)
objectId : <objectId>
subtype : map
type : object
}
}
{
result : {
className : ThisIsB
description : ThisIsB(0)
objectId : <objectId>
subtype : map
type : object
}
}
{
result : {
className : ThisIsC
description : ThisIsC(0)
objectId : <objectId>
subtype : map
type : object
}
}
// Copyright 2021 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.
let {session, contextGroup, Protocol} = InspectorTest.start('Regression test for crbug.com/980018');
contextGroup.addInlineScript(`
class A extends Map {
}
class B extends Map {
constructor() {
super();
Object.defineProperty(this, Symbol.toStringTag, {value: 'ThisIsB'});
}
}
class C extends Map {
}
Object.defineProperty(C.prototype, Symbol.toStringTag, {value: 'ThisIsC'});`);
InspectorTest.runAsyncTestSuite([
async function testClassNames() {
await Protocol.Runtime.enable();
await Promise.all(['A', 'B', 'C'].map(klass => Protocol.Runtime.evaluate({expression: `new ${klass}();`}).then(({result}) => InspectorTest.logMessage(result))));
await Protocol.Runtime.disable();
}
]);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment