[class] support out-of-scope private member access in debug-evaluate

Previously in the DevTools console, users could inspect a preview of all private class members on an instance, but if they wanted to evaluate or inspect a  specific private class member out of a long list, they had to be debugging and in a scope that has access to those private names.

This patch adds support for extraordinary access of out-of-scope private member access in debug-evaluate, specifically for Debugger.evaluateOnCallframe() (for console calls invoked during debugging) and Runtime.evaluate() (for console calls invoked when the user is not debugging). This kind of access is not otherwise allowed in normal execution, but in the DevTools console it makes sense to relax the rules a bit for a better developer experience.

To support this kind of extraordinary access, if the parsing_while_debugging or is_repl_mode flag is set, when we encounter a private name reference that's in a top-level scope or an eval scope under a top-level scope, instead of throwing immediately, we bind the reference to a dynamic lookup variable, and emit bytecode that calls to %GetPrivateName() or %SetPrivateName() in the runtime to perform lookup of the private name as well as the load/store operations accordingly.

If there are more than on private name on the receiver matching the description (for example, an object with two `#field` private names from different classes), we throw an error for the ambiguity (we can consider supporting selection among the conflicting private names later, for the initial support we just throw for simplicity).

If there are no matching private names, or if the found private class member does not support the desired operation (e.g. attempting to write to a read-only private accessor), we throw an error as well.

If there is exactly one matching private name, and the found private class member support the desired operation, we dispatch to the proper behavior in the runtime calls.

Doc: https://docs.google.com/document/d/1Va89BKHjCDs9RccDWhuZBb6LyRMAd6BXM3-p25oHd8I/edit

Bug: chromium:1381806
Change-Id: I7d1db709470246050d2e4c2a85b2292e63c01fe9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4020267
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85421}
This commit is contained in:
Joyee Cheung 2023-01-19 15:29:20 +01:00 committed by V8 LUCI CQ
parent d2ff82cbc9
commit 4d0d31f41b
31 changed files with 1763 additions and 37 deletions

View File

@ -1613,7 +1613,9 @@ enum AssignType {
PRIVATE_METHOD, // obj.#key: #key is a private method
PRIVATE_GETTER_ONLY, // obj.#key: #key only has a getter defined
PRIVATE_SETTER_ONLY, // obj.#key: #key only has a setter defined
PRIVATE_GETTER_AND_SETTER // obj.#key: #key has both accessors defined
PRIVATE_GETTER_AND_SETTER, // obj.#key: #key has both accessors defined
PRIVATE_DEBUG_DYNAMIC, // obj.#key: #key is private that requries dynamic
// lookup in debug-evaluate.
};
class Property final : public Expression {
@ -1650,6 +1652,9 @@ class Property final : public Expression {
return PRIVATE_SETTER_ONLY;
case VariableMode::kPrivateGetterAndSetter:
return PRIVATE_GETTER_AND_SETTER;
case VariableMode::kDynamic:
// From debug-evaluate.
return PRIVATE_DEBUG_DYNAMIC;
default:
UNREACHABLE();
}

View File

@ -1367,6 +1367,10 @@ void AstPrinter::VisitProperty(Property* node) {
PrintIndentedVisit("KEY", node->key());
break;
}
case PRIVATE_DEBUG_DYNAMIC: {
PrintIndentedVisit("PRIVATE_DEBUG_DYNAMIC", node->key());
break;
}
case NON_PROPERTY:
UNREACHABLE();
}

View File

@ -2061,6 +2061,15 @@ Variable* Scope::NonLocal(const AstRawString* name, VariableMode mode) {
return var;
}
void Scope::ForceDynamicLookup(VariableProxy* proxy) {
// At the moment this is only used for looking up private names dynamically
// in debug-evaluate from top-level scope.
DCHECK(proxy->IsPrivateName());
DCHECK(is_script_scope() || is_module_scope() || is_eval_scope());
Variable* dynamic = NonLocal(proxy->raw_name(), VariableMode::kDynamic);
proxy->BindTo(dynamic);
}
// static
template <Scope::ScopeLookupMode mode>
Variable* Scope::Lookup(VariableProxy* proxy, Scope* scope,
@ -3112,6 +3121,13 @@ void PrivateNameScopeIterator::AddUnresolvedPrivateName(VariableProxy* proxy) {
// be new.
DCHECK(!proxy->is_resolved());
DCHECK(proxy->IsPrivateName());
// Use dynamic lookup for top-level scopes in debug-evaluate.
if (Done()) {
start_scope_->ForceDynamicLookup(proxy);
return;
}
GetScope()->EnsureRareData()->unresolved_private_names.Add(proxy);
// Any closure scope that contain uses of private names that skips over a
// class scope due to heritage expressions need private name context chain

View File

@ -637,6 +637,8 @@ class V8_EXPORT_PRIVATE Scope : public NON_EXPORTED_BASE(ZoneObject) {
return nullptr;
}
void ForceDynamicLookup(VariableProxy* proxy);
protected:
explicit Scope(Zone* zone);

View File

@ -13,6 +13,9 @@ namespace internal {
#define MESSAGE_TEMPLATES(T) \
/* Error */ \
T(None, "") \
T(ConflictingPrivateName, \
"Operation is ambiguous because there are more than one private name" \
"'%' on the object") \
T(CyclicProto, "Cyclic __proto__ value") \
T(Debugger, "Debugger: %") \
T(DebuggerLoading, "Error loading debugger") \
@ -149,6 +152,7 @@ namespace internal {
T(NonObjectAssertOption, "The 'assert' option must be an object") \
T(NonObjectInInstanceOfCheck, \
"Right-hand side of 'instanceof' is not an object") \
T(NonObjectPrivateNameAccess, "Cannot access private name % from %") \
T(NonObjectPropertyLoad, "Cannot read properties of %") \
T(NonObjectPropertyLoadWithProperty, \
"Cannot read properties of % (reading '%')") \

View File

@ -3638,8 +3638,15 @@ void BytecodeGenerator::BuildVariableLoad(Variable* variable,
feedback_index(slot), depth);
break;
}
default:
default: {
// Normally, private names should not be looked up dynamically,
// but we make an exception in debug-evaluate, in that case the
// lookup will be done in %SetPrivateMember() and %GetPrivateMember()
// calls, not here.
DCHECK(!variable->raw_name()->IsPrivateName());
builder()->LoadLookupSlot(variable->raw_name(), typeof_mode);
break;
}
}
break;
}
@ -3944,6 +3951,14 @@ BytecodeGenerator::AssignmentLhsData::PrivateMethodOrAccessor(
}
// static
BytecodeGenerator::AssignmentLhsData
BytecodeGenerator::AssignmentLhsData::PrivateDebugEvaluate(AssignType type,
Property* property,
Register object) {
return AssignmentLhsData(type, property, RegisterList(), object, Register(),
nullptr, nullptr);
}
// static
BytecodeGenerator::AssignmentLhsData
BytecodeGenerator::AssignmentLhsData::KeyedSuperProperty(
RegisterList super_property_args) {
return AssignmentLhsData(KEYED_SUPER_PROPERTY, nullptr, super_property_args,
@ -3984,6 +3999,13 @@ BytecodeGenerator::AssignmentLhsData BytecodeGenerator::PrepareAssignmentLhs(
return AssignmentLhsData::PrivateMethodOrAccessor(assign_type, property,
object, key);
}
case PRIVATE_DEBUG_DYNAMIC: {
AccumulatorPreservingScope scope(this, accumulator_preserving_mode);
Register object = VisitForRegisterValue(property->obj());
// Do not visit the key here, instead we will look them up at run time.
return AssignmentLhsData::PrivateDebugEvaluate(assign_type, property,
object);
}
case NAMED_SUPER_PROPERTY: {
AccumulatorPreservingScope scope(this, accumulator_preserving_mode);
RegisterList super_property_args =
@ -4560,6 +4582,16 @@ void BytecodeGenerator::BuildAssignment(
}
break;
}
case PRIVATE_DEBUG_DYNAMIC: {
Register value = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(value);
Property* property = lhs_data.expr()->AsProperty();
BuildPrivateDebugDynamicSet(property, lhs_data.object(), value);
if (!execution_result()->IsEffect()) {
builder()->LoadAccumulatorWithRegister(value);
}
break;
}
}
}
@ -4631,6 +4663,11 @@ void BytecodeGenerator::VisitCompoundAssignment(CompoundAssignment* expr) {
lhs_data.expr()->AsProperty());
break;
}
case PRIVATE_DEBUG_DYNAMIC: {
Property* property = lhs_data.expr()->AsProperty();
BuildPrivateDebugDynamicGet(property, lhs_data.object());
break;
}
}
BinaryOperation* binop = expr->binary_operation();
@ -5143,9 +5180,41 @@ void BytecodeGenerator::VisitPropertyLoad(Register obj, Property* property) {
VisitForAccumulatorValue(property->key());
break;
}
case PRIVATE_DEBUG_DYNAMIC: {
BuildPrivateDebugDynamicGet(property, obj);
break;
}
}
}
void BytecodeGenerator::BuildPrivateDebugDynamicGet(Property* property,
Register obj) {
RegisterAllocationScope scope(this);
RegisterList args = register_allocator()->NewRegisterList(2);
Variable* private_name = property->key()->AsVariableProxy()->var();
builder()
->MoveRegister(obj, args[0])
.LoadLiteral(private_name->raw_name())
.StoreAccumulatorInRegister(args[1])
.CallRuntime(Runtime::kGetPrivateMember, args);
}
void BytecodeGenerator::BuildPrivateDebugDynamicSet(Property* property,
Register obj,
Register value) {
RegisterAllocationScope scope(this);
RegisterList args = register_allocator()->NewRegisterList(3);
Variable* private_name = property->key()->AsVariableProxy()->var();
builder()
->MoveRegister(obj, args[0])
.LoadLiteral(private_name->raw_name())
.StoreAccumulatorInRegister(args[1])
.MoveRegister(value, args[2])
.CallRuntime(Runtime::kSetPrivateMember, args);
}
void BytecodeGenerator::BuildPrivateGetterAccess(Register object,
Register accessor_pair) {
RegisterAllocationScope scope(this);
@ -6081,6 +6150,11 @@ void BytecodeGenerator::VisitCountOperation(CountOperation* expr) {
BuildPrivateGetterAccess(object, key);
break;
}
case PRIVATE_DEBUG_DYNAMIC: {
object = VisitForRegisterValue(property->obj());
BuildPrivateDebugDynamicGet(property, object);
break;
}
}
// Save result for postfix expressions.
@ -6161,6 +6235,12 @@ void BytecodeGenerator::VisitCountOperation(CountOperation* expr) {
}
break;
}
case PRIVATE_DEBUG_DYNAMIC: {
Register value = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(value);
BuildPrivateDebugDynamicSet(property, object, value);
break;
}
}
// Restore old value for postfix expressions.

View File

@ -102,17 +102,24 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Property* property,
Register object,
Register key);
static AssignmentLhsData PrivateDebugEvaluate(AssignType type,
Property* property,
Register object);
static AssignmentLhsData NamedSuperProperty(
RegisterList super_property_args);
static AssignmentLhsData KeyedSuperProperty(
RegisterList super_property_args);
AssignType assign_type() const { return assign_type_; }
Expression* expr() const {
DCHECK(assign_type_ == NON_PROPERTY || assign_type_ == PRIVATE_METHOD ||
bool is_private_assign_type() const {
return assign_type_ == PRIVATE_METHOD ||
assign_type_ == PRIVATE_GETTER_ONLY ||
assign_type_ == PRIVATE_SETTER_ONLY ||
assign_type_ == PRIVATE_GETTER_AND_SETTER);
assign_type_ == PRIVATE_GETTER_AND_SETTER ||
assign_type_ == PRIVATE_DEBUG_DYNAMIC;
}
Expression* expr() const {
DCHECK(assign_type_ == NON_PROPERTY || is_private_assign_type());
return expr_;
}
Expression* object_expr() const {
@ -121,17 +128,12 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
}
Register object() const {
DCHECK(assign_type_ == NAMED_PROPERTY || assign_type_ == KEYED_PROPERTY ||
assign_type_ == PRIVATE_METHOD ||
assign_type_ == PRIVATE_GETTER_ONLY ||
assign_type_ == PRIVATE_SETTER_ONLY ||
assign_type_ == PRIVATE_GETTER_AND_SETTER);
is_private_assign_type());
return object_;
}
Register key() const {
DCHECK(assign_type_ == KEYED_PROPERTY || assign_type_ == PRIVATE_METHOD ||
assign_type_ == PRIVATE_GETTER_ONLY ||
assign_type_ == PRIVATE_SETTER_ONLY ||
assign_type_ == PRIVATE_GETTER_AND_SETTER);
DCHECK((assign_type_ == KEYED_PROPERTY || is_private_assign_type()) &&
assign_type_ != PRIVATE_DEBUG_DYNAMIC);
return key_;
}
const AstRawString* name() const {
@ -332,6 +334,9 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void BuildPrivateGetterAccess(Register obj, Register access_pair);
void BuildPrivateSetterAccess(Register obj, Register access_pair,
Register value);
void BuildPrivateDebugDynamicGet(Property* property, Register obj);
void BuildPrivateDebugDynamicSet(Property* property, Register obj,
Register value);
void BuildPrivateMethods(ClassLiteral* expr, bool is_static,
Register home_object);
void BuildClassProperty(ClassLiteral::Property* property);

View File

@ -305,6 +305,8 @@ class ParserBase {
// The current Zone, which might be the main zone or a temporary Zone.
Zone* zone() const { return zone_; }
V8_INLINE bool IsExtraordinaryPrivateNameAccessAllowed() const;
protected:
friend class v8::internal::ExpressionScope<ParserTypes<Impl>>;
friend class v8::internal::ExpressionParsingScope<ParserTypes<Impl>>;
@ -1759,6 +1761,39 @@ typename ParserBase<Impl>::IdentifierT ParserBase<Impl>::ParsePropertyName() {
return impl()->EmptyIdentifierString();
}
template <typename Impl>
bool ParserBase<Impl>::IsExtraordinaryPrivateNameAccessAllowed() const {
if (flags().parsing_while_debugging() != ParsingWhileDebugging::kYes &&
!flags().is_repl_mode()) {
return false;
}
Scope* current_scope = scope();
while (current_scope != nullptr) {
switch (current_scope->scope_type()) {
case CLASS_SCOPE:
case CATCH_SCOPE:
case BLOCK_SCOPE:
case WITH_SCOPE:
case SHADOW_REALM_SCOPE:
return false;
// Top-level scopes.
case SCRIPT_SCOPE:
case MODULE_SCOPE:
return true;
// Top-level wrapper function scopes.
case FUNCTION_SCOPE:
return function_literal_id_ == kFunctionLiteralIdTopLevel;
// Used by debug-evaluate. If the outer scope is top-level,
// extraordinary private name access is allowed.
case EVAL_SCOPE:
current_scope = current_scope->outer_scope();
DCHECK_NOT_NULL(current_scope);
break;
}
}
UNREACHABLE();
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParsePropertyOrPrivatePropertyName() {
@ -1780,7 +1815,10 @@ ParserBase<Impl>::ParsePropertyOrPrivatePropertyName() {
PrivateNameScopeIterator private_name_scope_iter(scope());
// Parse the identifier so that we can display it in the error message
name = impl()->GetIdentifier();
if (private_name_scope_iter.Done()) {
// In debug-evaluate, we relax the private name resolution to enable
// evaluation of obj.#member outside the class bodies in top-level scopes.
if (private_name_scope_iter.Done() &&
!IsExtraordinaryPrivateNameAccessAllowed()) {
impl()->ReportMessageAt(Scanner::Location(pos, pos + 1),
MessageTemplate::kInvalidPrivateFieldResolution,
impl()->GetRawNameFromIdentifier(name));

View File

@ -1565,7 +1565,7 @@ class PreParser : public ParserBase<PreParser> {
return PreParserExpression::StringLiteral();
}
PreParserExpression ExpressionFromPrivateName(
V8_INLINE PreParserExpression ExpressionFromPrivateName(
PrivateNameScopeIterator* private_name_scope,
const PreParserIdentifier& name, int start_position) {
VariableProxy* proxy = factory()->ast_node_factory()->NewVariableProxy(

View File

@ -1491,6 +1491,231 @@ RUNTIME_FUNCTION(Runtime_GetOwnPropertyDescriptorObject) {
return *desc.ToPropertyDescriptorObject(isolate);
}
enum class PrivateMemberType {
kPrivateField,
kPrivateAccessor,
kPrivateMethod,
};
struct PrivateMember {
PrivateMemberType type;
// It's the class constructor for static methods/accessors,
// the brand symbol for instance methods/accessors,
// and the private name symbol for fields.
Handle<Object> brand_or_field_symbol;
Handle<Object> value;
};
namespace {
void CollectPrivateMethodsAndAccessorsFromContext(
Isolate* isolate, Handle<Context> context, Handle<String> desc,
Handle<Object> brand, IsStaticFlag is_static_flag,
std::vector<PrivateMember>* results) {
Handle<ScopeInfo> scope_info(context->scope_info(), isolate);
VariableLookupResult lookup_result;
int context_index = scope_info->ContextSlotIndex(desc, &lookup_result);
if (context_index == -1 ||
!IsPrivateMethodOrAccessorVariableMode(lookup_result.mode) ||
lookup_result.is_static_flag != is_static_flag) {
return;
}
Handle<Object> slot_value(context->get(context_index), isolate);
DCHECK_IMPLIES(lookup_result.mode == VariableMode::kPrivateMethod,
slot_value->IsJSFunction());
DCHECK_IMPLIES(lookup_result.mode != VariableMode::kPrivateMethod,
slot_value->IsAccessorPair());
results->push_back({
lookup_result.mode == VariableMode::kPrivateMethod
? PrivateMemberType::kPrivateMethod
: PrivateMemberType::kPrivateAccessor,
brand,
slot_value,
});
}
Maybe<bool> CollectPrivateMembersFromReceiver(
Isolate* isolate, Handle<JSReceiver> receiver, Handle<String> desc,
std::vector<PrivateMember>* results) {
PropertyFilter key_filter =
static_cast<PropertyFilter>(PropertyFilter::PRIVATE_NAMES_ONLY);
Handle<FixedArray> keys;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, keys,
KeyAccumulator::GetKeys(isolate, receiver, KeyCollectionMode::kOwnOnly,
key_filter, GetKeysConversion::kConvertToString),
Nothing<bool>());
if (receiver->IsJSFunction()) {
Handle<JSFunction> func(JSFunction::cast(*receiver), isolate);
Handle<SharedFunctionInfo> shared(func->shared(), isolate);
if (shared->is_class_constructor() &&
shared->has_static_private_methods_or_accessors()) {
Handle<Context> recevier_context(JSFunction::cast(*receiver).context(),
isolate);
CollectPrivateMethodsAndAccessorsFromContext(
isolate, recevier_context, desc, func, IsStaticFlag::kStatic,
results);
}
}
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> obj_key(keys->get(i), isolate);
Handle<Symbol> symbol(Symbol::cast(*obj_key), isolate);
CHECK(symbol->is_private_name());
Handle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, Object::GetProperty(isolate, receiver, symbol),
Nothing<bool>());
if (symbol->is_private_brand()) {
Handle<Context> value_context(Context::cast(*value), isolate);
CollectPrivateMethodsAndAccessorsFromContext(
isolate, value_context, desc, symbol, IsStaticFlag::kNotStatic,
results);
} else {
Handle<String> symbol_desc(String::cast(symbol->description()), isolate);
if (symbol_desc->Equals(*desc)) {
results->push_back({
PrivateMemberType::kPrivateField,
symbol,
value,
});
}
}
}
return Just(true);
}
Maybe<bool> FindPrivateMembersFromReceiver(Isolate* isolate,
Handle<JSReceiver> receiver,
Handle<String> desc,
MessageTemplate not_found_message,
PrivateMember* result) {
std::vector<PrivateMember> results;
MAYBE_RETURN(
CollectPrivateMembersFromReceiver(isolate, receiver, desc, &results),
Nothing<bool>());
if (results.size() == 0) {
THROW_NEW_ERROR_RETURN_VALUE(isolate, NewError(not_found_message, desc),
Nothing<bool>());
} else if (results.size() > 1) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewError(MessageTemplate::kConflictingPrivateName, desc),
Nothing<bool>());
}
*result = results[0];
return Just(true);
}
} // namespace
MaybeHandle<Object> Runtime::GetPrivateMember(Isolate* isolate,
Handle<JSReceiver> receiver,
Handle<String> desc) {
PrivateMember result;
MAYBE_RETURN_NULL(FindPrivateMembersFromReceiver(
isolate, receiver, desc, MessageTemplate::kInvalidPrivateMemberRead,
&result));
switch (result.type) {
case PrivateMemberType::kPrivateField:
case PrivateMemberType::kPrivateMethod: {
return result.value;
}
case PrivateMemberType::kPrivateAccessor: {
// The accessors are collected from the contexts, so there is no need to
// perform brand checks.
Handle<AccessorPair> pair = Handle<AccessorPair>::cast(result.value);
if (pair->getter().IsNull()) {
THROW_NEW_ERROR(
isolate,
NewError(MessageTemplate::kInvalidPrivateGetterAccess, desc),
Object);
}
DCHECK(pair->getter().IsJSFunction());
Handle<JSFunction> getter(JSFunction::cast(pair->getter()), isolate);
return Execution::Call(isolate, getter, receiver, 0, nullptr);
}
}
}
MaybeHandle<Object> Runtime::SetPrivateMember(Isolate* isolate,
Handle<JSReceiver> receiver,
Handle<String> desc,
Handle<Object> value) {
PrivateMember result;
MAYBE_RETURN_NULL(FindPrivateMembersFromReceiver(
isolate, receiver, desc, MessageTemplate::kInvalidPrivateMemberRead,
&result));
switch (result.type) {
case PrivateMemberType::kPrivateField: {
Handle<Symbol> symbol =
Handle<Symbol>::cast(result.brand_or_field_symbol);
return Object::SetProperty(isolate, receiver, symbol, value,
StoreOrigin::kMaybeKeyed);
}
case PrivateMemberType::kPrivateMethod: {
THROW_NEW_ERROR(
isolate, NewError(MessageTemplate::kInvalidPrivateMethodWrite, desc),
Object);
}
case PrivateMemberType::kPrivateAccessor: {
// The accessors are collected from the contexts, so there is no need to
// perform brand checks.
Handle<AccessorPair> pair = Handle<AccessorPair>::cast(result.value);
if (pair->setter().IsNull()) {
THROW_NEW_ERROR(
isolate,
NewError(MessageTemplate::kInvalidPrivateSetterAccess, desc),
Object);
}
DCHECK(pair->setter().IsJSFunction());
Handle<Object> argv[] = {value};
Handle<JSFunction> setter(JSFunction::cast(pair->setter()), isolate);
return Execution::Call(isolate, setter, receiver, arraysize(argv), argv);
}
}
}
RUNTIME_FUNCTION(Runtime_GetPrivateMember) {
HandleScope scope(isolate);
// TODO(chromium:1381806) support specifying scopes, or selecting the right
// one from the conflicting names.
DCHECK_EQ(args.length(), 2);
Handle<Object> receiver = args.at<Object>(0);
Handle<String> desc = args.at<String>(1);
if (receiver->IsNullOrUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNonObjectPrivateNameAccess,
desc, receiver));
}
RETURN_RESULT_OR_FAILURE(
isolate, Runtime::GetPrivateMember(
isolate, Handle<JSReceiver>::cast(receiver), desc));
}
RUNTIME_FUNCTION(Runtime_SetPrivateMember) {
HandleScope scope(isolate);
// TODO(chromium:1381806) support specifying scopes, or selecting the right
// one from the conflicting names.
DCHECK_EQ(args.length(), 3);
Handle<Object> receiver = args.at<Object>(0);
Handle<String> desc = args.at<String>(1);
if (receiver->IsNullOrUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNonObjectPrivateNameAccess,
desc, receiver));
}
Handle<Object> value = args.at<Object>(2);
RETURN_RESULT_OR_FAILURE(
isolate, Runtime::SetPrivateMember(
isolate, Handle<JSReceiver>::cast(receiver), desc, value));
}
RUNTIME_FUNCTION(Runtime_LoadPrivateSetter) {
HandleScope scope(isolate);
DCHECK_EQ(args.length(), 1);

View File

@ -319,6 +319,7 @@ namespace internal {
F(GetOwnPropertyDescriptor, 2, 1) \
F(GetOwnPropertyDescriptorObject, 2, 1) \
F(GetOwnPropertyKeys, 2, 1) \
F(GetPrivateMember, 2, 1) \
F(GetProperty, -1 /* [2, 3] */, 1) \
F(HasFastPackedElements, 1, 1) \
F(HasInPrototypeChain, 2, 1) \
@ -360,6 +361,7 @@ namespace internal {
F(ToObject, 1, 1) \
F(ToString, 1, 1) \
F(TryMigrateInstance, 1, 1) \
F(SetPrivateMember, 3, 1) \
F(SwissTableAdd, 4, 1) \
F(SwissTableAllocate, 1, 1) \
F(SwissTableDelete, 2, 1) \
@ -878,6 +880,29 @@ class Runtime : public AllStatic {
Handle<Object> receiver = Handle<Object>(),
bool* is_found = nullptr);
// Look up for a private member with a name matching "desc" and return its
// value. "desc" should be a #-prefixed string, in the case of private fields,
// it should match the description of the private name symbol. Throw an error
// if the found private member is an accessor without a getter, or there is no
// matching private member, or there are more than one matching private member
// (which would be ambiguous). If the found private member is an accessor with
// a getter, the getter will be called to set the value.
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
GetPrivateMember(Isolate* isolate, Handle<JSReceiver> receiver,
Handle<String> desc);
// Look up for a private member with a name matching "desc" and set it to
// "value". "desc" should be a #-prefixed string, in the case of private
// fields, it should match the description of the private name symbol. Throw
// an error if the found private member is a private method, or an accessor
// without a setter, or there is no matching private member, or there are more
// than one matching private member (which would be ambiguous).
// If the found private member is an accessor with a setter, the setter will
// be called to set the value.
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
SetPrivateMember(Isolate* isolate, Handle<JSReceiver> receiver,
Handle<String> desc, Handle<Object> value);
V8_WARN_UNUSED_RESULT static MaybeHandle<Object> HasProperty(
Isolate* isolate, Handle<Object> object, Handle<Object> key);

View File

@ -0,0 +1,55 @@
Evaluate conflicting private class member out of class scope in Debugger.evaluateOnCallFrame()
class Klass {
#name = "string";
}
class ClassWithField extends Klass {
#name = "child";
}
class ClassWithMethod extends Klass {
#name() {}
}
class ClassWithAccessor extends Klass {
get #name() {}
set #name(val) {}
}
class StaticClass extends Klass {
static #name = "child";
}
debugger;
Running test: evaluatePrivateMembers
Debugger.evaluateOnCallFrame: `(new ClassWithField).#name`
{
className : Error
description : Error: Operation is ambiguous because there are more than one private name'#name' on the object at eval (eval at <anonymous> (:18:1), <anonymous>:1:2) at <anonymous>:18:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `(new ClassWithMethod).#name`
{
className : Error
description : Error: Operation is ambiguous because there are more than one private name'#name' on the object at eval (eval at <anonymous> (:18:1), <anonymous>:1:2) at <anonymous>:18:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `(new ClassWithAccessor).#name`
{
className : Error
description : Error: Operation is ambiguous because there are more than one private name'#name' on the object at eval (eval at <anonymous> (:18:1), <anonymous>:1:2) at <anonymous>:18:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `StaticClass.#name`
{
type : string
value : child
}
Debugger.evaluateOnCallFrame: `(new StaticClass).#name`
{
type : string
value : string
}

View File

@ -0,0 +1,12 @@
// Copyright 2022 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.
utils.load('test/inspector/private-class-member-inspector-test.js');
const options = {
type: 'private-conflicting-member',
testRuntime: false,
message: `Evaluate conflicting private class member out of class scope in Debugger.evaluateOnCallFrame()`
};
PrivateClassMemberInspectorTest.runTest(InspectorTest, options);

View File

@ -0,0 +1,231 @@
Evaluate private class member out of class scope in Debugger.evaluateOnCallFrame()
class Klass {
#field = "string";
get #getterOnly() { return "getterOnly"; }
set #setterOnly(val) { this.#field = "setterOnlyCalled"; }
get #accessor() { return this.#field }
set #accessor(val) { this.#field = val; }
#method() { return "method"; }
}
const obj = new Klass();
debugger;
Running test: evaluatePrivateMembers
Checking private fields
Debugger.evaluateOnCallFrame: `obj.#field`
{
type : string
value : string
}
Debugger.evaluateOnCallFrame: `obj.#field = 1`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#field++`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 2
type : number
value : 2
}
Debugger.evaluateOnCallFrame: `++obj.#field`
{
description : 3
type : number
value : 3
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 3
type : number
value : 3
}
Debugger.evaluateOnCallFrame: `obj.#field -= 3`
{
description : 0
type : number
value : 0
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 0
type : number
value : 0
}
Checking private getter-only accessors
Debugger.evaluateOnCallFrame: `obj.#getterOnly`
{
type : string
value : getterOnly
}
Debugger.evaluateOnCallFrame: `obj.#getterOnly = 1`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at eval (eval at <anonymous> (:11:1), <anonymous>:1:17) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#getterOnly++`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at eval (eval at <anonymous> (:11:1), <anonymous>:1:16) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#getterOnly -= 3`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at eval (eval at <anonymous> (:11:1), <anonymous>:1:17) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#getterOnly`
{
type : string
value : getterOnly
}
Checking private setter-only accessors
Debugger.evaluateOnCallFrame: `obj.#setterOnly`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at eval (eval at <anonymous> (:11:1), <anonymous>:1:1) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#setterOnly = 1`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#setterOnly++`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at eval (eval at <anonymous> (:11:1), <anonymous>:1:1) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#setterOnly -= 3`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at eval (eval at <anonymous> (:11:1), <anonymous>:1:1) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
type : string
value : setterOnlyCalled
}
Checking private accessors
Debugger.evaluateOnCallFrame: `obj.#accessor`
{
type : string
value : setterOnlyCalled
}
Debugger.evaluateOnCallFrame: `obj.#accessor = 1`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#accessor++`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 2
type : number
value : 2
}
Debugger.evaluateOnCallFrame: `++obj.#accessor`
{
type : undefined
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 3
type : number
value : 3
}
Debugger.evaluateOnCallFrame: `obj.#accessor -= 3`
{
description : 0
type : number
value : 0
}
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 0
type : number
value : 0
}
Checking private methods
Debugger.evaluateOnCallFrame: `obj.#method`
{
className : Function
description : #method() { return "method"; }
objectId : <objectId>
type : function
}
Debugger.evaluateOnCallFrame: `obj.#method = 1`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:13) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#method++`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:12) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `++obj.#method`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:7) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `obj.#method -= 3`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:13) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}

View File

@ -0,0 +1,16 @@
Evaluate private class member out of class scope in Debugger.evaluateOnCallFrame() in module
class Klass {
#field = 1;
}
const obj = new Klass;
debugger;
Running test: evaluatePrivateMembers
Debugger.evaluateOnCallFrame: `obj.#field`
{
description : 1
type : number
value : 1
}

View File

@ -0,0 +1,32 @@
// Copyright 2022 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.
const { contextGroup, Protocol } = InspectorTest.start(
'Evaluate private class member out of class scope in Debugger.evaluateOnCallFrame() in module'
);
Protocol.Debugger.enable();
const source = `
class Klass {
#field = 1;
}
const obj = new Klass;
debugger;
`;
InspectorTest.log(source);
contextGroup.addModule(source, 'module');
InspectorTest.runAsyncTestSuite([async function evaluatePrivateMembers() {
const { params: { callFrames } } = await Protocol.Debugger.oncePaused();
const frame = callFrames[0];
const expression = 'obj.#field';
InspectorTest.log(`Debugger.evaluateOnCallFrame: \`${expression}\``);
const { result: { result } } =
await Protocol.Debugger.evaluateOnCallFrame({
callFrameId: frame.callFrameId,
expression
});
InspectorTest.logMessage(result);
}]);

View File

@ -0,0 +1,231 @@
Evaluate static private class member out of class scope in Debugger.evaluateOnCallFrame()
class Klass {
static #field = "string";
static get #getterOnly() { return "getterOnly"; }
static set #setterOnly(val) { this.#field = "setterOnlyCalled"; }
static get #accessor() { return this.#field }
static set #accessor(val) { this.#field = val; }
static #method() { return "method"; }
}
const obj = new Klass();
debugger;
Running test: evaluatePrivateMembers
Checking private fields
Debugger.evaluateOnCallFrame: `Klass.#field`
{
type : string
value : string
}
Debugger.evaluateOnCallFrame: `Klass.#field = 1`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#field++`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 2
type : number
value : 2
}
Debugger.evaluateOnCallFrame: `++Klass.#field`
{
description : 3
type : number
value : 3
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 3
type : number
value : 3
}
Debugger.evaluateOnCallFrame: `Klass.#field -= 3`
{
description : 0
type : number
value : 0
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 0
type : number
value : 0
}
Checking private getter-only accessors
Debugger.evaluateOnCallFrame: `Klass.#getterOnly`
{
type : string
value : getterOnly
}
Debugger.evaluateOnCallFrame: `Klass.#getterOnly = 1`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at eval (eval at <anonymous> (:11:1), <anonymous>:1:19) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#getterOnly++`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at eval (eval at <anonymous> (:11:1), <anonymous>:1:18) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#getterOnly -= 3`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at eval (eval at <anonymous> (:11:1), <anonymous>:1:19) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#getterOnly`
{
type : string
value : getterOnly
}
Checking private setter-only accessors
Debugger.evaluateOnCallFrame: `Klass.#setterOnly`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at eval (eval at <anonymous> (:11:1), <anonymous>:1:1) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#setterOnly = 1`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#setterOnly++`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at eval (eval at <anonymous> (:11:1), <anonymous>:1:1) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#setterOnly -= 3`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at eval (eval at <anonymous> (:11:1), <anonymous>:1:1) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
type : string
value : setterOnlyCalled
}
Checking private accessors
Debugger.evaluateOnCallFrame: `Klass.#accessor`
{
type : string
value : setterOnlyCalled
}
Debugger.evaluateOnCallFrame: `Klass.#accessor = 1`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#accessor++`
{
description : 1
type : number
value : 1
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 2
type : number
value : 2
}
Debugger.evaluateOnCallFrame: `++Klass.#accessor`
{
type : undefined
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 3
type : number
value : 3
}
Debugger.evaluateOnCallFrame: `Klass.#accessor -= 3`
{
description : 0
type : number
value : 0
}
Debugger.evaluateOnCallFrame: `Klass.#field`
{
description : 0
type : number
value : 0
}
Checking private methods
Debugger.evaluateOnCallFrame: `Klass.#method`
{
className : Function
description : #method() { return "method"; }
objectId : <objectId>
type : function
}
Debugger.evaluateOnCallFrame: `Klass.#method = 1`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:15) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#method++`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:14) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `++Klass.#method`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:9) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}
Debugger.evaluateOnCallFrame: `Klass.#method -= 3`
{
className : Error
description : Error: Private method '#method' is not writable at eval (eval at <anonymous> (:11:1), <anonymous>:1:15) at <anonymous>:11:1
objectId : <objectId>
subtype : error
type : object
}

View File

@ -0,0 +1,12 @@
// Copyright 2022 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.
utils.load('test/inspector/private-class-member-inspector-test.js');
const options = {
type: 'private-static-member',
testRuntime: false,
message: `Evaluate static private class member out of class scope in Debugger.evaluateOnCallFrame()`
};
PrivateClassMemberInspectorTest.runTest(InspectorTest, options);

View File

@ -0,0 +1,12 @@
// Copyright 2022 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.
utils.load('test/inspector/private-class-member-inspector-test.js');
const options = {
type: 'private-instance-member',
testRuntime: false,
message: `Evaluate private class member out of class scope in Debugger.evaluateOnCallFrame()`
};
PrivateClassMemberInspectorTest.runTest(InspectorTest, options);

View File

@ -0,0 +1,167 @@
// Copyright 2022 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.
PrivateClassMemberInspectorTest = {};
function getTestReceiver(type) {
return type === 'private-instance-member' ? 'obj' : 'Klass';
}
function getSetupScript({ type, testRuntime }) {
const pause = testRuntime ? '' : 'debugger;';
if (type === 'private-instance-member' || type === 'private-static-member') {
const isStatic = type === 'private-static-member';
const prefix = isStatic ? 'static' : '';
return `
class Klass {
${prefix} #field = "string";
${prefix} get #getterOnly() { return "getterOnly"; }
${prefix} set #setterOnly(val) { this.#field = "setterOnlyCalled"; }
${prefix} get #accessor() { return this.#field }
${prefix} set #accessor(val) { this.#field = val; }
${prefix} #method() { return "method"; }
}
const obj = new Klass();
${pause}`;
}
if (type !== 'private-conflicting-member') {
throw new Error('unknown test type');
}
return `
class Klass {
#name = "string";
}
class ClassWithField extends Klass {
#name = "child";
}
class ClassWithMethod extends Klass {
#name() {}
}
class ClassWithAccessor extends Klass {
get #name() {}
set #name(val) {}
}
class StaticClass extends Klass {
static #name = "child";
}
${pause}`;
}
async function testAllPrivateMembers(type, runAndLog) {
const receiver = getTestReceiver(type);
InspectorTest.log('Checking private fields');
await runAndLog(`${receiver}.#field`);
await runAndLog(`${receiver}.#field = 1`);
await runAndLog(`${receiver}.#field`);
await runAndLog(`${receiver}.#field++`);
await runAndLog(`${receiver}.#field`);
await runAndLog(`++${receiver}.#field`);
await runAndLog(`${receiver}.#field`);
await runAndLog(`${receiver}.#field -= 3`);
await runAndLog(`${receiver}.#field`);
InspectorTest.log('Checking private getter-only accessors');
await runAndLog(`${receiver}.#getterOnly`);
await runAndLog(`${receiver}.#getterOnly = 1`);
await runAndLog(`${receiver}.#getterOnly++`);
await runAndLog(`${receiver}.#getterOnly -= 3`);
await runAndLog(`${receiver}.#getterOnly`);
InspectorTest.log('Checking private setter-only accessors');
await runAndLog(`${receiver}.#setterOnly`);
await runAndLog(`${receiver}.#setterOnly = 1`);
await runAndLog(`${receiver}.#setterOnly++`);
await runAndLog(`${receiver}.#setterOnly -= 3`);
await runAndLog(`${receiver}.#field`);
InspectorTest.log('Checking private accessors');
await runAndLog(`${receiver}.#accessor`);
await runAndLog(`${receiver}.#accessor = 1`);
await runAndLog(`${receiver}.#field`);
await runAndLog(`${receiver}.#accessor++`);
await runAndLog(`${receiver}.#field`);
await runAndLog(`++${receiver}.#accessor`);
await runAndLog(`${receiver}.#field`);
await runAndLog(`${receiver}.#accessor -= 3`);
await runAndLog(`${receiver}.#field`);
InspectorTest.log('Checking private methods');
await runAndLog(`${receiver}.#method`);
await runAndLog(`${receiver}.#method = 1`);
await runAndLog(`${receiver}.#method++`);
await runAndLog(`++${receiver}.#method`);
await runAndLog(`${receiver}.#method -= 3`);
}
async function testConflictingPrivateMembers(runAndLog) {
await runAndLog(`(new ClassWithField).#name`);
await runAndLog(`(new ClassWithMethod).#name`);
await runAndLog(`(new ClassWithAccessor).#name`);
await runAndLog(`StaticClass.#name`);
await runAndLog(`(new StaticClass).#name`);
}
async function runPrivateClassMemberTest(Protocol, { type, testRuntime }) {
let runAndLog;
if (testRuntime) {
runAndLog = async function runAndLog(expression) {
InspectorTest.log(`Runtime.evaluate: \`${expression}\``);
const { result: { result } } =
await Protocol.Runtime.evaluate({ expression, replMode: true });
InspectorTest.logMessage(result);
}
} else {
const { params: { callFrames } } = await Protocol.Debugger.oncePaused();
const frame = callFrames[0];
runAndLog = async function runAndLog(expression) {
InspectorTest.log(`Debugger.evaluateOnCallFrame: \`${expression}\``);
const { result: { result } } =
await Protocol.Debugger.evaluateOnCallFrame({
callFrameId: frame.callFrameId,
expression
});
InspectorTest.logMessage(result);
}
}
switch (type) {
case 'private-instance-member':
case 'private-static-member': {
await testAllPrivateMembers(type, runAndLog);
break;
}
case 'private-conflicting-member': {
await testConflictingPrivateMembers(runAndLog);
break;
}
default:
throw new Error('unknown test type');
}
await Protocol.Debugger.resume();
}
PrivateClassMemberInspectorTest.runTest = function (InspectorTest, options) {
const { contextGroup, Protocol } = InspectorTest.start(options.message);
if (options.testRuntime) {
Protocol.Runtime.enable();
} else {
Protocol.Debugger.enable();
}
const source = getSetupScript(options);
InspectorTest.log(source);
if (options.module) {
contextGroup.addModule(source, 'module');
} else {
contextGroup.addScript(source);
}
InspectorTest.runAsyncTestSuite([async function evaluatePrivateMembers() {
await runPrivateClassMemberTest(Protocol, options);
}]);
}

View File

@ -0,0 +1,55 @@
Evaluate conflicting private class member out of class scope in Runtime.evaluate()
class Klass {
#name = "string";
}
class ClassWithField extends Klass {
#name = "child";
}
class ClassWithMethod extends Klass {
#name() {}
}
class ClassWithAccessor extends Klass {
get #name() {}
set #name(val) {}
}
class StaticClass extends Klass {
static #name = "child";
}
Running test: evaluatePrivateMembers
Runtime.evaluate: `(new ClassWithField).#name`
{
className : Error
description : Error: Operation is ambiguous because there are more than one private name'#name' on the object at <anonymous>:1:2
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `(new ClassWithMethod).#name`
{
className : Error
description : Error: Operation is ambiguous because there are more than one private name'#name' on the object at <anonymous>:1:2
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `(new ClassWithAccessor).#name`
{
className : Error
description : Error: Operation is ambiguous because there are more than one private name'#name' on the object at <anonymous>:1:2
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `StaticClass.#name`
{
type : string
value : child
}
Runtime.evaluate: `(new StaticClass).#name`
{
type : string
value : string
}

View File

@ -0,0 +1,12 @@
// Copyright 2022 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.
utils.load('test/inspector/private-class-member-inspector-test.js');
const options = {
type: 'private-conflicting-member',
testRuntime: true,
message: `Evaluate conflicting private class member out of class scope in Runtime.evaluate()`
};
PrivateClassMemberInspectorTest.runTest(InspectorTest, options);

View File

@ -0,0 +1,231 @@
Evaluate private class member out of class scope in Runtime.evaluate()
class Klass {
#field = "string";
get #getterOnly() { return "getterOnly"; }
set #setterOnly(val) { this.#field = "setterOnlyCalled"; }
get #accessor() { return this.#field }
set #accessor(val) { this.#field = val; }
#method() { return "method"; }
}
const obj = new Klass();
Running test: evaluatePrivateMembers
Checking private fields
Runtime.evaluate: `obj.#field`
{
type : string
value : string
}
Runtime.evaluate: `obj.#field = 1`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#field`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#field++`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#field`
{
description : 2
type : number
value : 2
}
Runtime.evaluate: `++obj.#field`
{
description : 3
type : number
value : 3
}
Runtime.evaluate: `obj.#field`
{
description : 3
type : number
value : 3
}
Runtime.evaluate: `obj.#field -= 3`
{
description : 0
type : number
value : 0
}
Runtime.evaluate: `obj.#field`
{
description : 0
type : number
value : 0
}
Checking private getter-only accessors
Runtime.evaluate: `obj.#getterOnly`
{
type : string
value : getterOnly
}
Runtime.evaluate: `obj.#getterOnly = 1`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at <anonymous>:1:17
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#getterOnly++`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at <anonymous>:1:16
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#getterOnly -= 3`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at <anonymous>:1:17
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#getterOnly`
{
type : string
value : getterOnly
}
Checking private setter-only accessors
Runtime.evaluate: `obj.#setterOnly`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#setterOnly = 1`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#setterOnly++`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#setterOnly -= 3`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#field`
{
type : string
value : setterOnlyCalled
}
Checking private accessors
Runtime.evaluate: `obj.#accessor`
{
type : string
value : setterOnlyCalled
}
Runtime.evaluate: `obj.#accessor = 1`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#field`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#accessor++`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `obj.#field`
{
description : 2
type : number
value : 2
}
Runtime.evaluate: `++obj.#accessor`
{
type : undefined
}
Runtime.evaluate: `obj.#field`
{
description : 3
type : number
value : 3
}
Runtime.evaluate: `obj.#accessor -= 3`
{
description : 0
type : number
value : 0
}
Runtime.evaluate: `obj.#field`
{
description : 0
type : number
value : 0
}
Checking private methods
Runtime.evaluate: `obj.#method`
{
className : Function
description : #method() { return "method"; }
objectId : <objectId>
type : function
}
Runtime.evaluate: `obj.#method = 1`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:13
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#method++`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:12
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `++obj.#method`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:7
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `obj.#method -= 3`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:13
objectId : <objectId>
subtype : error
type : object
}

View File

@ -0,0 +1,231 @@
Evaluate static private class member out of class scope in Runtime.evaluate()
class Klass {
static #field = "string";
static get #getterOnly() { return "getterOnly"; }
static set #setterOnly(val) { this.#field = "setterOnlyCalled"; }
static get #accessor() { return this.#field }
static set #accessor(val) { this.#field = val; }
static #method() { return "method"; }
}
const obj = new Klass();
Running test: evaluatePrivateMembers
Checking private fields
Runtime.evaluate: `Klass.#field`
{
type : string
value : string
}
Runtime.evaluate: `Klass.#field = 1`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#field`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#field++`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#field`
{
description : 2
type : number
value : 2
}
Runtime.evaluate: `++Klass.#field`
{
description : 3
type : number
value : 3
}
Runtime.evaluate: `Klass.#field`
{
description : 3
type : number
value : 3
}
Runtime.evaluate: `Klass.#field -= 3`
{
description : 0
type : number
value : 0
}
Runtime.evaluate: `Klass.#field`
{
description : 0
type : number
value : 0
}
Checking private getter-only accessors
Runtime.evaluate: `Klass.#getterOnly`
{
type : string
value : getterOnly
}
Runtime.evaluate: `Klass.#getterOnly = 1`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at <anonymous>:1:19
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#getterOnly++`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at <anonymous>:1:18
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#getterOnly -= 3`
{
className : Error
description : Error: '#getterOnly' was defined without a setter at <anonymous>:1:19
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#getterOnly`
{
type : string
value : getterOnly
}
Checking private setter-only accessors
Runtime.evaluate: `Klass.#setterOnly`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#setterOnly = 1`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#setterOnly++`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#setterOnly -= 3`
{
className : Error
description : Error: '#setterOnly' was defined without a getter at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#field`
{
type : string
value : setterOnlyCalled
}
Checking private accessors
Runtime.evaluate: `Klass.#accessor`
{
type : string
value : setterOnlyCalled
}
Runtime.evaluate: `Klass.#accessor = 1`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#field`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#accessor++`
{
description : 1
type : number
value : 1
}
Runtime.evaluate: `Klass.#field`
{
description : 2
type : number
value : 2
}
Runtime.evaluate: `++Klass.#accessor`
{
type : undefined
}
Runtime.evaluate: `Klass.#field`
{
description : 3
type : number
value : 3
}
Runtime.evaluate: `Klass.#accessor -= 3`
{
description : 0
type : number
value : 0
}
Runtime.evaluate: `Klass.#field`
{
description : 0
type : number
value : 0
}
Checking private methods
Runtime.evaluate: `Klass.#method`
{
className : Function
description : #method() { return "method"; }
objectId : <objectId>
type : function
}
Runtime.evaluate: `Klass.#method = 1`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:15
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#method++`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:14
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `++Klass.#method`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:9
objectId : <objectId>
subtype : error
type : object
}
Runtime.evaluate: `Klass.#method -= 3`
{
className : Error
description : Error: Private method '#method' is not writable at <anonymous>:1:15
objectId : <objectId>
subtype : error
type : object
}

View File

@ -0,0 +1,12 @@
// Copyright 2022 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.
utils.load('test/inspector/private-class-member-inspector-test.js');
const options = {
type: 'private-static-member',
testRuntime: true,
message: `Evaluate static private class member out of class scope in Runtime.evaluate()`
};
PrivateClassMemberInspectorTest.runTest(InspectorTest, options);

View File

@ -0,0 +1,12 @@
// Copyright 2022 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.
utils.load('test/inspector/private-class-member-inspector-test.js');
const options = {
type: 'private-instance-member',
testRuntime: true,
message: `Evaluate private class member out of class scope in Runtime.evaluate()`
};
PrivateClassMemberInspectorTest.runTest(InspectorTest, options);

View File

@ -11,6 +11,7 @@ from testrunner.outproc import base as outproc
PROTOCOL_TEST_JS = "protocol-test.js"
WASM_INSPECTOR_JS = "wasm-inspector-test.js"
PRIVATE_MEMBER_TEST_JS = "private-class-member-inspector-test.js"
EXPECTED_SUFFIX = "-expected.txt"
RESOURCES_FOLDER = "resources"
@ -18,7 +19,7 @@ RESOURCES_FOLDER = "resources"
class TestLoader(testsuite.JSTestLoader):
@property
def excluded_files(self):
return {PROTOCOL_TEST_JS, WASM_INSPECTOR_JS}
return {PROTOCOL_TEST_JS, WASM_INSPECTOR_JS, PRIVATE_MEMBER_TEST_JS}
@property
def excluded_dirs(self):

View File

@ -83,7 +83,7 @@ bytecodes: [
/* 48 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 53 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 58 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(308),
B(Wide), B(LdaSmi), I16(310),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
@ -115,7 +115,7 @@ bytecodes: [
/* 41 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 46 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 51 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(307),
B(Wide), B(LdaSmi), I16(309),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
@ -149,7 +149,7 @@ bytecodes: [
B(Star2),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 58 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(308),
B(Wide), B(LdaSmi), I16(310),
B(Star3),
B(LdaConstant), U8(0),
B(Star4),
@ -181,7 +181,7 @@ bytecodes: [
/* 41 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 46 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 51 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(307),
B(Wide), B(LdaSmi), I16(309),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),

View File

@ -58,7 +58,7 @@ bytecodes: [
B(Star2),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 54 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(306),
B(Wide), B(LdaSmi), I16(308),
B(Star3),
B(LdaConstant), U8(0),
B(Star4),
@ -91,7 +91,7 @@ bytecodes: [
/* 44 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 49 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 54 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(306),
B(Wide), B(LdaSmi), I16(308),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),

View File

@ -24,7 +24,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(1),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
@ -61,13 +61,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
/* 61 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(306),
B(Wide), B(LdaSmi), I16(308),
B(Star2),
B(LdaConstant), U8(1),
B(Star3),
@ -99,13 +99,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
/* 61 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(1), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(306),
B(Wide), B(LdaSmi), I16(308),
B(Star1),
B(LdaConstant), U8(1),
B(Star2),
@ -145,7 +145,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
@ -167,7 +167,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star3),
B(LdaConstant), U8(0),
B(Star4),
@ -182,7 +182,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
@ -216,13 +216,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
/* 65 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(1), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(308),
B(Wide), B(LdaSmi), I16(310),
B(Star1),
B(LdaConstant), U8(1),
B(Star2),
@ -253,13 +253,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
/* 58 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(1), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(307),
B(Wide), B(LdaSmi), I16(309),
B(Star1),
B(LdaConstant), U8(1),
B(Star2),
@ -292,13 +292,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(302),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
/* 65 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(308),
B(Wide), B(LdaSmi), I16(310),
B(Star2),
B(LdaConstant), U8(1),
B(Star3),
@ -327,7 +327,7 @@ bytecode array length: 19
bytecodes: [
/* 46 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 51 E> */ B(GetKeyedProperty), R(this), U8(0),
B(Wide), B(LdaSmi), I16(307),
B(Wide), B(LdaSmi), I16(309),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),