LibJS: Infer computed property function names
Use a runtime SetFunctionName bytecode operation when object literal property keys are not known until evaluation. This lets anonymous function and class expressions, methods, and accessors receive names from numeric, computed, and Symbol property keys. Store inferred ECMAScript function names on each function object instead of mutating shared function data. That keeps repeated evaluations with different computed keys from leaking names across closures, while still using the per-instance name for stack metadata. Add regression coverage for computed object property names, repeated computed-key evaluations, and preserving unnamed functions that are only referenced by a computed property value.
This commit is contained in:
parent
5c881d1553
commit
7a246b63c7
16 changed files with 203 additions and 42 deletions
|
|
@ -468,6 +468,8 @@ i64 asm_fallback_handler(VM* vm, u32 pc)
|
|||
return execute_throwing<Op::PutBySpread>(*vm, pc);
|
||||
case Instruction::Type::PutByValueWithThis:
|
||||
return execute_throwing<Op::PutByValueWithThis>(*vm, pc);
|
||||
case Instruction::Type::SetFunctionName:
|
||||
return execute_throwing<Op::SetFunctionName>(*vm, pc);
|
||||
case Instruction::Type::ResolveSuperBase:
|
||||
return execute_throwing<Op::ResolveSuperBase>(*vm, pc);
|
||||
case Instruction::Type::DynamicSetLexicalBinding:
|
||||
|
|
|
|||
|
|
@ -1035,6 +1035,12 @@ op SetCompletionType < Instruction
|
|||
m_completion_type: Completion::Type
|
||||
endop
|
||||
|
||||
op SetFunctionName < Instruction
|
||||
m_function: Operand
|
||||
m_name: Operand
|
||||
m_prefix: FunctionNamePrefix
|
||||
endop
|
||||
|
||||
op SetGlobal < Instruction
|
||||
m_identifier: IdentifierTableIndex
|
||||
m_src: Operand
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ enum class ArgumentsKind {
|
|||
Unmapped,
|
||||
};
|
||||
|
||||
enum class FunctionNamePrefix {
|
||||
None,
|
||||
Get,
|
||||
Set,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
|
|
|||
|
|
@ -763,6 +763,7 @@ void VM::run_bytecode(size_t entry_point)
|
|||
HANDLE_INSTRUCTION(ResolveThisBinding);
|
||||
HANDLE_INSTRUCTION(RightShift);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(SetCompletionType);
|
||||
HANDLE_INSTRUCTION(SetFunctionName);
|
||||
HANDLE_INSTRUCTION(SetGlobal);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(SetLexicalEnvironment);
|
||||
HANDLE_INSTRUCTION(SetLexicalBinding);
|
||||
|
|
@ -3222,6 +3223,30 @@ void NewFunction::execute_impl(VM& vm) const
|
|||
vm.set(dst(), new_function(vm, m_shared_function_data_index, m_home_object));
|
||||
}
|
||||
|
||||
static Optional<StringView> function_name_prefix_to_string(Op::FunctionNamePrefix prefix)
|
||||
{
|
||||
switch (prefix) {
|
||||
case Op::FunctionNamePrefix::None:
|
||||
return {};
|
||||
case Op::FunctionNamePrefix::Get:
|
||||
return "get"sv;
|
||||
case Op::FunctionNamePrefix::Set:
|
||||
return "set"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> SetFunctionName::execute_impl(VM& vm) const
|
||||
{
|
||||
auto function = vm.get(m_function).as_if<ECMAScriptFunctionObject>();
|
||||
if (!function || !function->name().is_empty())
|
||||
return {};
|
||||
|
||||
auto property_key = TRY(vm.get(m_name).to_property_key(vm));
|
||||
function->set_inferred_name(Variant<PropertyKey, PrivateName> { move(property_key) }, function_name_prefix_to_string(m_prefix));
|
||||
return {};
|
||||
}
|
||||
|
||||
void Return::execute_impl(VM& vm) const
|
||||
{
|
||||
vm.do_return(vm.get(m_value));
|
||||
|
|
|
|||
|
|
@ -218,14 +218,14 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
|
|||
case PutKind::Getter: {
|
||||
auto& function = value.as_function();
|
||||
if (is<ECMAScriptFunctionObject>(function) && static_cast<ECMAScriptFunctionObject const&>(function).name().is_empty())
|
||||
static_cast<ECMAScriptFunctionObject*>(&function)->set_name(Utf16String::formatted("get {}", name));
|
||||
static_cast<ECMAScriptFunctionObject*>(&function)->set_inferred_name(Variant<PropertyKey, PrivateName> { name }, "get"sv);
|
||||
object->define_direct_accessor(name, &function, nullptr, Attribute::Configurable | Attribute::Enumerable);
|
||||
break;
|
||||
}
|
||||
case PutKind::Setter: {
|
||||
auto& function = value.as_function();
|
||||
if (is<ECMAScriptFunctionObject>(function) && static_cast<ECMAScriptFunctionObject const&>(function).name().is_empty())
|
||||
static_cast<ECMAScriptFunctionObject*>(&function)->set_name(Utf16String::formatted("set {}", name));
|
||||
static_cast<ECMAScriptFunctionObject*>(&function)->set_inferred_name(Variant<PropertyKey, PrivateName> { name }, "set"sv);
|
||||
object->define_direct_accessor(name, nullptr, &function, Attribute::Configurable | Attribute::Enumerable);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ static constexpr u32 put_kind_variant_count = to_underlying(PutKind::Own) + 1;
|
|||
static_assert(put_kind_variant_count == 5);
|
||||
static constexpr u32 arguments_kind_variant_count = to_underlying(Op::ArgumentsKind::Unmapped) + 1;
|
||||
static_assert(arguments_kind_variant_count == 2);
|
||||
static constexpr u32 function_name_prefix_variant_count = to_underlying(Op::FunctionNamePrefix::Set) + 1;
|
||||
static_assert(function_name_prefix_variant_count == 3);
|
||||
|
||||
ErrorOr<void> validate_bytecode(Executable const& executable, ReadonlySpan<u32> basic_block_offsets)
|
||||
{
|
||||
|
|
@ -122,6 +124,7 @@ ErrorOr<void> validate_bytecode(Executable const& executable, ReadonlySpan<u32>
|
|||
.environment_mode_variant_count = environment_mode_variant_count,
|
||||
.put_kind_variant_count = put_kind_variant_count,
|
||||
.arguments_kind_variant_count = arguments_kind_variant_count,
|
||||
.function_name_prefix_variant_count = function_name_prefix_variant_count,
|
||||
};
|
||||
|
||||
// Project Executable's exception handlers down to plain offsets; the
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ pub fn field_type_info(ty: &str) -> FieldType {
|
|||
"EnvironmentMode" => ("u32", 4, 4, "u32"),
|
||||
"PutKind" => ("u32", 4, 4, "u32"),
|
||||
"ArgumentsKind" => ("u32", 4, 4, "u32"),
|
||||
"FunctionNamePrefix" => ("u32", 4, 4, "u32"),
|
||||
"Value" => ("u64", 8, 8, "u64"),
|
||||
"PropertyLookupCacheIndex"
|
||||
| "GlobalVariableCacheIndex"
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
static void update_function_name(Value value, Utf16FlyString const& name)
|
||||
static void update_function_name(Value value, ClassElementName const& name, Optional<StringView> const& prefix = {})
|
||||
{
|
||||
if (auto function = value.as_if<ECMAScriptFunctionObject>(); function && function->name().is_empty())
|
||||
function->set_name(name);
|
||||
function->set_inferred_name(name, prefix);
|
||||
}
|
||||
|
||||
static ThrowCompletionOr<ClassElementName> resolve_element_key(VM& vm, Bytecode::ClassElementDescriptor const& descriptor, Value property_key)
|
||||
|
|
@ -42,25 +42,6 @@ static ThrowCompletionOr<ClassElementName> resolve_element_key(VM& vm, Bytecode:
|
|||
return ClassElementName { key };
|
||||
}
|
||||
|
||||
static Utf16String compute_element_name(ClassElementName const& element_name, StringView prefix = {})
|
||||
{
|
||||
auto name = element_name.visit(
|
||||
[&](PropertyKey const& property_key) {
|
||||
if (property_key.is_symbol()) {
|
||||
auto description = property_key.as_symbol()->description();
|
||||
if (!description.has_value() || description->is_empty())
|
||||
return Utf16String {};
|
||||
return Utf16String::formatted("[{}]", *description);
|
||||
}
|
||||
return property_key.to_string();
|
||||
},
|
||||
[&](PrivateName const& private_name) {
|
||||
return private_name.description.to_utf16_string();
|
||||
});
|
||||
|
||||
return Utf16String::formatted("{}{}{}", prefix, prefix.is_empty() ? "" : " ", name);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<ECMAScriptFunctionObject*> construct_class(
|
||||
VM& vm,
|
||||
Bytecode::ClassBlueprint const& blueprint,
|
||||
|
|
@ -155,19 +136,19 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> construct_class(
|
|||
auto& property_key = element_name.get<PropertyKey>();
|
||||
switch (descriptor.kind) {
|
||||
case Bytecode::ClassElementDescriptor::Kind::Method: {
|
||||
update_function_name(method_value, compute_element_name(element_name));
|
||||
update_function_name(method_value, element_name);
|
||||
PropertyDescriptor property_descriptor { .value = method_value, .writable = true, .enumerable = false, .configurable = true };
|
||||
TRY(home_object.define_property_or_throw(property_key, property_descriptor));
|
||||
break;
|
||||
}
|
||||
case Bytecode::ClassElementDescriptor::Kind::Getter: {
|
||||
update_function_name(method_value, compute_element_name(element_name, "get"sv));
|
||||
update_function_name(method_value, element_name, "get"sv);
|
||||
PropertyDescriptor property_descriptor { .get = &method_function, .enumerable = false, .configurable = true };
|
||||
TRY(home_object.define_property_or_throw(property_key, property_descriptor));
|
||||
break;
|
||||
}
|
||||
case Bytecode::ClassElementDescriptor::Kind::Setter: {
|
||||
update_function_name(method_value, compute_element_name(element_name, "set"sv));
|
||||
update_function_name(method_value, element_name, "set"sv);
|
||||
PropertyDescriptor property_descriptor { .set = &method_function, .enumerable = false, .configurable = true };
|
||||
TRY(home_object.define_property_or_throw(property_key, property_descriptor));
|
||||
break;
|
||||
|
|
@ -182,13 +163,13 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> construct_class(
|
|||
PrivateElement private_element = [&] {
|
||||
switch (descriptor.kind) {
|
||||
case Bytecode::ClassElementDescriptor::Kind::Method:
|
||||
update_function_name(method_value, compute_element_name(element_name));
|
||||
update_function_name(method_value, element_name);
|
||||
return PrivateElement { private_name, PrivateElement::Kind::Method, method_value };
|
||||
case Bytecode::ClassElementDescriptor::Kind::Getter:
|
||||
update_function_name(method_value, compute_element_name(element_name, "get"sv));
|
||||
update_function_name(method_value, element_name, "get"sv);
|
||||
return PrivateElement { private_name, PrivateElement::Kind::Accessor, Value(Accessor::create(vm, &method_function, nullptr)) };
|
||||
case Bytecode::ClassElementDescriptor::Kind::Setter:
|
||||
update_function_name(method_value, compute_element_name(element_name, "set"sv));
|
||||
update_function_name(method_value, element_name, "set"sv);
|
||||
return PrivateElement { private_name, PrivateElement::Kind::Accessor, Value(Accessor::create(vm, nullptr, &method_function)) };
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ void ECMAScriptFunctionObject::get_stack_frame_info(size_t& registers_and_locals
|
|||
VERIFY(rust_executable);
|
||||
m_shared_data->set_executable(rust_executable);
|
||||
executable = rust_executable;
|
||||
executable->name = m_shared_data->m_name;
|
||||
executable->name = name();
|
||||
if (Bytecode::g_dump_bytecode)
|
||||
executable->dump();
|
||||
m_shared_data->clear_compile_inputs();
|
||||
|
|
@ -533,12 +533,18 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::ordinary_call_evaluate_body(V
|
|||
void ECMAScriptFunctionObject::set_name(Utf16FlyString const& name)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
const_cast<SharedFunctionInstanceData&>(shared_data()).m_name = name;
|
||||
m_name = name;
|
||||
m_name_string = PrimitiveString::create(vm, name);
|
||||
PropertyDescriptor descriptor { .value = m_name_string, .writable = false, .enumerable = false, .configurable = true };
|
||||
MUST(define_property_or_throw(vm.names.name, descriptor));
|
||||
}
|
||||
|
||||
void ECMAScriptFunctionObject::set_inferred_name(Variant<PropertyKey, PrivateName> const& name, Optional<StringView> const& prefix)
|
||||
{
|
||||
auto function_name = make_function_name(name, prefix);
|
||||
set_name(Utf16FlyString(function_name->utf16_string()));
|
||||
}
|
||||
|
||||
ECMAScriptFunctionObject::ClassData& ECMAScriptFunctionObject::ensure_class_data() const
|
||||
{
|
||||
if (!m_class_data)
|
||||
|
|
|
|||
|
|
@ -59,8 +59,14 @@ public:
|
|||
|
||||
virtual Utf16String name_for_call_stack() const override;
|
||||
|
||||
Utf16FlyString const& name() const { return shared_data().m_name; }
|
||||
Utf16FlyString const& name() const
|
||||
{
|
||||
if (m_name.has_value())
|
||||
return *m_name;
|
||||
return shared_data().m_name;
|
||||
}
|
||||
void set_name(Utf16FlyString const& name);
|
||||
void set_inferred_name(Variant<PropertyKey, PrivateName> const& name, Optional<StringView> const& prefix = {});
|
||||
|
||||
void set_is_class_constructor() { const_cast<SharedFunctionInstanceData&>(shared_data()).set_is_class_constructor(); }
|
||||
|
||||
|
|
@ -155,6 +161,7 @@ private:
|
|||
|
||||
GC::Ref<SharedFunctionInstanceData> m_shared_data;
|
||||
|
||||
Optional<Utf16FlyString> m_name;
|
||||
GC::Ptr<PrimitiveString> m_name_string;
|
||||
|
||||
// Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects
|
||||
|
|
|
|||
|
|
@ -258,6 +258,10 @@ fn emit_scalar_field_check(
|
|||
w,
|
||||
" validate_arguments_kind(read_u32(bytes, at + {offset}), ctx)?;"
|
||||
)?,
|
||||
"FunctionNamePrefix" => writeln!(
|
||||
w,
|
||||
" validate_function_name_prefix(read_u32(bytes, at + {offset}), ctx)?;"
|
||||
)?,
|
||||
// bool, u64, Value, EnvironmentCoordinate, Builtin: no per-field
|
||||
// bound applied here.
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -1256,6 +1256,13 @@ enum ArgumentsKind {
|
|||
Unmapped = 1,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
enum FunctionNamePrefix {
|
||||
None = 0,
|
||||
Get = 1,
|
||||
Set = 2,
|
||||
}
|
||||
|
||||
/// Class element kind (ABI-compatible with ClassBlueprint::Element::Kind).
|
||||
#[repr(u8)]
|
||||
enum ClassElementKind {
|
||||
|
|
@ -5110,6 +5117,27 @@ fn emit_switch_block_declaration_instantiation(generator: &mut Generator, data:
|
|||
// Object expression
|
||||
// =============================================================================
|
||||
|
||||
fn is_anonymous_function_definition(generator: &Generator, expression: &Expression) -> bool {
|
||||
match &expression.inner {
|
||||
ExpressionKind::Function(function_id) => generator.function_table.get(*function_id).name.is_none(),
|
||||
ExpressionKind::Class(data) => data.name.is_none(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_set_function_name(
|
||||
generator: &mut Generator,
|
||||
function: &ScopedOperand,
|
||||
name: &ScopedOperand,
|
||||
prefix: FunctionNamePrefix,
|
||||
) {
|
||||
generator.emit(Instruction::SetFunctionName {
|
||||
function: function.operand(),
|
||||
name: name.operand(),
|
||||
prefix: prefix as u32,
|
||||
});
|
||||
}
|
||||
|
||||
/// Generate bytecode for an object literal expression.
|
||||
///
|
||||
/// Objects whose shape can be determined at compile time (only simple
|
||||
|
|
@ -5188,6 +5216,25 @@ fn generate_object_expression(
|
|||
None
|
||||
};
|
||||
|
||||
let is_method_like = property.is_method
|
||||
|| property.property_type == ObjectPropertyType::Getter
|
||||
|| property.property_type == ObjectPropertyType::Setter;
|
||||
let function_name_prefix = match property.property_type {
|
||||
ObjectPropertyType::Getter => FunctionNamePrefix::Get,
|
||||
ObjectPropertyType::Setter => FunctionNamePrefix::Set,
|
||||
_ => FunctionNamePrefix::None,
|
||||
};
|
||||
// PropertyDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation
|
||||
// If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and _isProtoSetter_ is *false*, then
|
||||
// Let _propValue_ be ? NamedEvaluation of |AssignmentExpression| with argument _propertyKey_.
|
||||
let should_set_runtime_function_name = computed_key.is_some()
|
||||
&& property.property_type != ObjectPropertyType::ProtoSetter
|
||||
&& (is_method_like
|
||||
|| property
|
||||
.value
|
||||
.as_ref()
|
||||
.is_some_and(|value| is_anonymous_function_definition(generator, value)));
|
||||
|
||||
// Set pending LHS name for function name inference on non-computed properties.
|
||||
// ProtoSetter (__proto__) skips NamedEvaluation per spec.
|
||||
if !effectively_computed && property.property_type != ObjectPropertyType::ProtoSetter {
|
||||
|
|
@ -5219,9 +5266,6 @@ fn generate_object_expression(
|
|||
}
|
||||
// Methods, getters, and setters need the object as their [[HomeObject]]
|
||||
// so that super property lookups work.
|
||||
let is_method_like = property.is_method
|
||||
|| property.property_type == ObjectPropertyType::Getter
|
||||
|| property.property_type == ObjectPropertyType::Setter;
|
||||
if is_method_like {
|
||||
generator.home_objects.push(dst.clone());
|
||||
}
|
||||
|
|
@ -5234,6 +5278,16 @@ fn generate_object_expression(
|
|||
generator.home_objects.pop();
|
||||
}
|
||||
generator.pending_lhs_name = None;
|
||||
if should_set_runtime_function_name {
|
||||
emit_set_function_name(
|
||||
generator,
|
||||
&value,
|
||||
computed_key
|
||||
.as_ref()
|
||||
.expect("runtime function names require a runtime property key"),
|
||||
function_name_prefix,
|
||||
);
|
||||
}
|
||||
|
||||
match property.property_type {
|
||||
ObjectPropertyType::Spread => {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ pub struct FFIValidatorBounds {
|
|||
pub environment_mode_variant_count: u32,
|
||||
pub put_kind_variant_count: u32,
|
||||
pub arguments_kind_variant_count: u32,
|
||||
pub function_name_prefix_variant_count: u32,
|
||||
}
|
||||
|
||||
/// Categorization of validation failures, mirrored to C++ as an enum class.
|
||||
|
|
@ -362,6 +363,14 @@ pub fn validate_arguments_kind(raw: u32, ctx: &ValidationContext) -> Result<(),
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn validate_function_name_prefix(raw: u32, ctx: &ValidationContext) -> Result<(), ValidationErrorKind> {
|
||||
if raw >= ctx.bounds.function_name_prefix_variant_count {
|
||||
return Err(ValidationErrorKind::EnumOutOfRange);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Walk `bytes` and verify the structural integrity of every instruction.
|
||||
///
|
||||
/// Pass 1 verifies that the buffer is a tight sequence of well-formed
|
||||
|
|
@ -519,6 +528,7 @@ mod tests {
|
|||
environment_mode_variant_count: 2,
|
||||
put_kind_variant_count: 5,
|
||||
arguments_kind_variant_count: 2,
|
||||
function_name_prefix_variant_count: 3,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ use crate::bytecode::validator::{
|
|||
use crate::{CompiledProgram, CompiledProgramBytecode, ModuleCallbacks, ast, u32_from_usize};
|
||||
|
||||
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
|
||||
const FORMAT_VERSION: u32 = 11;
|
||||
const FORMAT_VERSION: u32 = 12;
|
||||
const SOURCE_HASH_SIZE: usize = 32;
|
||||
const BYTECODE_ALIGNMENT: usize = 8;
|
||||
const COMPLETION_TYPE_VARIANT_COUNT: u32 = 6;
|
||||
|
|
@ -37,6 +37,7 @@ const ITERATOR_HINT_VARIANT_COUNT: u32 = 2;
|
|||
const ENVIRONMENT_MODE_VARIANT_COUNT: u32 = 2;
|
||||
const PUT_KIND_VARIANT_COUNT: u32 = 5;
|
||||
const ARGUMENTS_KIND_VARIANT_COUNT: u32 = 2;
|
||||
const FUNCTION_NAME_PREFIX_VARIANT_COUNT: u32 = 3;
|
||||
|
||||
fn source_span_is_valid(start: u32, end: u32, source_len: usize) -> bool {
|
||||
let start = start as usize;
|
||||
|
|
@ -2403,6 +2404,7 @@ impl DecodedExecutableRecord {
|
|||
environment_mode_variant_count: ENVIRONMENT_MODE_VARIANT_COUNT,
|
||||
put_kind_variant_count: PUT_KIND_VARIANT_COUNT,
|
||||
arguments_kind_variant_count: ARGUMENTS_KIND_VARIANT_COUNT,
|
||||
function_name_prefix_variant_count: FUNCTION_NAME_PREFIX_VARIANT_COUNT,
|
||||
};
|
||||
|
||||
let exception_handlers = self
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
$7f6ce915 numeric-getter-no-lhs-name.js:1:1
|
||||
$87b8bc45 numeric-getter-no-lhs-name.js:1:1
|
||||
Registers: 7
|
||||
Blocks: 1
|
||||
Constants:
|
||||
|
|
@ -8,11 +8,12 @@ block0:
|
|||
[ 0] NewObject dst:reg5
|
||||
[ 10] ToPrimitiveWithStringHint dst:Int32(0), value:Int32(0)
|
||||
[ 20] NewFunction dst:reg6, shared_function_data_index:0, home_object:reg5
|
||||
[ 38] PutByValue base:reg5, property:Int32(0), src:reg6, kind:Getter
|
||||
[ 50] SetGlobal `obj`, src:reg5
|
||||
[ 60] GetGlobal dst:reg5, `obj`
|
||||
[ 70] GetByValue dst:reg6, base:reg5, property:Int32(0) (obj[Int32(0)])
|
||||
[ 88] End value:reg6
|
||||
[ 38] SetFunctionName function:reg6, name:Int32(0)
|
||||
[ 48] PutByValue base:reg5, property:Int32(0), src:reg6, kind:Getter
|
||||
[ 60] SetGlobal `obj`, src:reg5
|
||||
[ 70] GetGlobal dst:reg5, `obj`
|
||||
[ 80] GetByValue dst:reg6, base:reg5, property:Int32(0) (obj[Int32(0)])
|
||||
[ 98] End value:reg6
|
||||
|
||||
|
||||
get 0$4c2eeb71 numeric-getter-no-lhs-name.js:3:9
|
||||
|
|
|
|||
|
|
@ -44,6 +44,59 @@ test("functions in objects", () => {
|
|||
expect(o.c.name).toBe("");
|
||||
});
|
||||
|
||||
test("computed property names infer anonymous function names", () => {
|
||||
const namedSymbol = Symbol("named");
|
||||
const emptySymbol = Symbol("");
|
||||
const unnamedSymbol = Symbol();
|
||||
const getterSymbol = Symbol("getter");
|
||||
const setterSymbol = Symbol("");
|
||||
|
||||
const object = {
|
||||
[1]: function () {},
|
||||
[2]: class {},
|
||||
[namedSymbol]: () => {},
|
||||
[emptySymbol]: function* () {},
|
||||
[unnamedSymbol]: async function () {},
|
||||
[/a/]: function () {},
|
||||
get [getterSymbol]() {},
|
||||
set [setterSymbol](value) {},
|
||||
};
|
||||
|
||||
expect(object[1].name).toBe("1");
|
||||
expect(object[2].name).toBe("2");
|
||||
expect(object[namedSymbol].name).toBe("[named]");
|
||||
expect(object[emptySymbol].name).toBe("[]");
|
||||
expect(object[unnamedSymbol].name).toBe("");
|
||||
expect(object[/a/].name).toBe("/a/");
|
||||
expect(Object.getOwnPropertyDescriptor(object, getterSymbol).get.name).toBe("get [getter]");
|
||||
expect(Object.getOwnPropertyDescriptor(object, setterSymbol).set.name).toBe("set []");
|
||||
});
|
||||
|
||||
test("computed property name inference does not leak between evaluations", () => {
|
||||
function objectName(key) {
|
||||
return { [key]: function () {} }[key].name;
|
||||
}
|
||||
|
||||
function classMethodName(key) {
|
||||
return new (class {
|
||||
[key]() {}
|
||||
})()[key].name;
|
||||
}
|
||||
|
||||
expect(objectName("first")).toBe("first");
|
||||
expect(objectName("second")).toBe("second");
|
||||
expect(classMethodName("first")).toBe("first");
|
||||
expect(classMethodName("second")).toBe("second");
|
||||
});
|
||||
|
||||
test("computed property name inference only applies to anonymous definitions", () => {
|
||||
const key = "computed";
|
||||
const value = [function () {}][0];
|
||||
const object = { [key]: value };
|
||||
|
||||
expect(object[key].name).toBe("");
|
||||
});
|
||||
|
||||
test("names of native functions", () => {
|
||||
expect(console.debug.name).toBe("debug");
|
||||
expect((console.debug.name = "warn")).toBe("warn");
|
||||
|
|
|
|||
Loading…
Reference in a new issue