[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:
parent
d2ff82cbc9
commit
4d0d31f41b
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 '%')") \
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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);
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
||||
}]);
|
@ -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
|
||||
}
|
@ -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);
|
@ -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);
|
167
test/inspector/private-class-member-inspector-test.js
Normal file
167
test/inspector/private-class-member-inspector-test.js
Normal 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);
|
||||
}]);
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
12
test/inspector/runtime/evaluate-private-class-member.js
Normal file
12
test/inspector/runtime/evaluate-private-class-member.js
Normal 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);
|
@ -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):
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user