/* * Copyright (c) 2020-2022, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace JS { GC_DEFINE_ALLOCATOR(DeclarativeEnvironment); DeclarativeEnvironment::DeclarativeEnvironment() : Environment(nullptr, IsDeclarative::Yes) { } DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment) : Environment(parent_environment, IsDeclarative::Yes) { } DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_environment, ReadonlySpan bindings) : Environment(parent_environment, IsDeclarative::Yes) { ensure_capacity(bindings.size()); for (auto binding : bindings) append_binding(move(binding)); } void DeclarativeEnvironment::RareData::visit_edges(Visitor& visitor) const { m_dispose_capability.visit_edges(visitor); } size_t DeclarativeEnvironment::RareData::external_memory_size() const { auto size = sizeof(RareData); size = saturating_add_external_memory_size(size, vector_external_memory_size(m_binding_names)); size = saturating_add_external_memory_size(size, vector_external_memory_size(m_binding_flags)); size = saturating_add_external_memory_size(size, m_deleted_bindings.size_in_bytes()); size = saturating_add_external_memory_size(size, hash_map_external_memory_size(m_bindings_assoc)); if (m_dispose_capability.disposable_resource_stack) { size = saturating_add_external_memory_size(size, sizeof(Vector)); size = saturating_add_external_memory_size(size, vector_external_memory_size(*m_dispose_capability.disposable_resource_stack)); } return size; } bool DeclarativeEnvironment::RareData::is_empty() const { return m_binding_names.is_empty() && m_binding_flags.is_empty() && m_deleted_bindings.is_null() && m_bindings_assoc.is_empty() && !m_dispose_capability.disposable_resource_stack && !m_environment_shape_cache && m_expected_binding_count == 0 && m_environment_serial_number == 0 && !m_is_catch_environment; } DeclarativeEnvironment::RareData& DeclarativeEnvironment::ensure_rare_data() { if (!m_rare_data) m_rare_data = make(); return *m_rare_data; } void DeclarativeEnvironment::drop_rare_data_if_empty() { if (m_rare_data && m_rare_data->is_empty()) m_rare_data.clear(); } void DeclarativeEnvironment::increment_environment_serial_number() { if (m_shape) return; if (m_rare_data && m_rare_data->m_environment_shape_cache) return; ++ensure_rare_data().m_environment_serial_number; } bool DeclarativeEnvironment::is_catch_environment() const { if (!m_rare_data) return false; return m_rare_data->m_is_catch_environment; } void DeclarativeEnvironment::set_is_catch_environment(bool value) { if (!value && !m_rare_data) return; ensure_rare_data().m_is_catch_environment = value; drop_rare_data_if_empty(); } void DeclarativeEnvironment::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); if (m_rare_data) m_rare_data->visit_edges(visitor); visitor.visit(m_shape); for (auto& value : m_binding_values) visitor.visit(value); } size_t DeclarativeEnvironment::external_memory_size() const { auto size = vector_external_memory_size(m_binding_values); if (m_rare_data) size = saturating_add_external_memory_size(size, m_rare_data->external_memory_size()); return size; } DisposeCapability const& DeclarativeEnvironment::dispose_capability() const { static NeverDestroyed empty_dispose_capability; if (!m_rare_data) return *empty_dispose_capability; return m_rare_data->m_dispose_capability; } DisposeCapability& DeclarativeEnvironment::dispose_capability() { return ensure_rare_data().m_dispose_capability; } DisposeCapability* DeclarativeEnvironment::dispose_capability_if_exists() { if (!m_rare_data || !m_rare_data->m_dispose_capability.disposable_resource_stack) return nullptr; return &m_rare_data->m_dispose_capability; } u64 DeclarativeEnvironment::environment_serial_number() const { if (!m_rare_data) return 0; return m_rare_data->m_environment_serial_number; } void DeclarativeEnvironment::append_binding(Binding binding) { auto index = m_binding_values.size(); u8 flags = 0; if (binding.strict) flags |= BindingFlagStrict; if (binding.mutable_) flags |= BindingFlagMutable; if (binding.can_be_deleted) flags |= BindingFlagCanBeDeleted; if (m_shape && index < shape_binding_count()) { VERIFY(m_shape->binding_name(index) == binding.name); VERIFY(m_shape->binding_flags(index) == flags); } else { auto& rare_data = ensure_rare_data(); rare_data.m_bindings_assoc.set(binding.name, index); rare_data.m_binding_names.append(move(binding.name)); rare_data.m_binding_flags.append(flags); } m_binding_values.append(binding.initialized ? binding.value : js_special_empty_value()); } void DeclarativeEnvironment::clear_binding(Utf16FlyString const& name, size_t index) { if (index < shape_binding_count()) { ensure_deleted_bindings_capacity(index + 1); m_rare_data->m_deleted_bindings.set(index, true); m_binding_values[index] = js_special_empty_value(); return; } VERIFY(m_rare_data); m_rare_data->m_bindings_assoc.remove(name); auto local_index = local_binding_index(index); m_rare_data->m_binding_names[local_index] = Utf16FlyString {}; m_binding_values[index] = js_special_empty_value(); m_rare_data->m_binding_flags[local_index] = 0; } DeclarativeEnvironment::Binding DeclarativeEnvironment::binding_at(size_t index) const { return Binding { .name = binding_name(index), .value = m_binding_values[index], .strict = binding_is_strict(index), .mutable_ = binding_is_mutable(index), .can_be_deleted = binding_can_be_deleted(index), .initialized = binding_is_initialized(index), }; } void DeclarativeEnvironment::set_environment_shape_cache(GC::Ptr& cache, size_t expected_binding_count) { if (expected_binding_count == 0) return; if (cache) { VERIFY(cache->size() == expected_binding_count); set_environment_shape(GC::Ref { *cache }); return; } auto& rare_data = ensure_rare_data(); rare_data.m_environment_shape_cache = &cache; rare_data.m_expected_binding_count = expected_binding_count; } void DeclarativeEnvironment::set_environment_shape(GC::Ref shape) { VERIFY(!m_shape); VERIFY(m_binding_values.size() <= shape->size()); m_shape = shape; if (m_rare_data) { m_rare_data->m_binding_names.clear(); m_rare_data->m_binding_flags.clear(); m_rare_data->m_bindings_assoc.clear(); m_rare_data->m_environment_shape_cache = nullptr; m_rare_data->m_expected_binding_count = 0; drop_rare_data_if_empty(); } } void DeclarativeEnvironment::maybe_finalize_environment_shape(VM& vm) { if (!m_rare_data || !m_rare_data->m_environment_shape_cache || m_shape || m_rare_data->m_expected_binding_count == 0 || m_binding_values.size() != m_rare_data->m_expected_binding_count) return; if (*m_rare_data->m_environment_shape_cache) { auto shape = GC::Ref { **m_rare_data->m_environment_shape_cache }; VERIFY(shape->size() == m_binding_values.size()); for (size_t i = 0; i < m_binding_values.size(); ++i) { VERIFY(shape->binding_name(i) == m_rare_data->m_binding_names[i]); VERIFY(shape->binding_flags(i) == m_rare_data->m_binding_flags[i]); } set_environment_shape(shape); return; } auto shape = EnvironmentShape::create(vm, m_rare_data->m_binding_names, m_rare_data->m_binding_flags); *m_rare_data->m_environment_shape_cache = shape; set_environment_shape(shape); } // 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n ThrowCompletionOr DeclarativeEnvironment::has_binding(Utf16FlyString const& name, Optional* out_index) const { auto binding_and_index = find_binding_and_index(name); if (!binding_and_index.has_value()) return false; if (!is_permanently_screwed_by_eval() && out_index && binding_and_index->index().has_value()) *out_index = *(binding_and_index->index()); return true; } // 9.1.1.1.2 CreateMutableBinding ( N, D ), https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d ThrowCompletionOr DeclarativeEnvironment::create_mutable_binding(VM& vm, Utf16FlyString const& name, bool can_be_deleted) { // 1. Assert: envRec does not already have a binding for N. // NOTE: We skip this to avoid O(n) traversal of m_binding_names. // 2. Create a mutable binding in envRec for N and record that it is uninitialized. If D is true, record that the newly created binding may be deleted by a subsequent DeleteBinding call. append_binding(Binding { .name = name, .value = {}, .strict = false, .mutable_ = true, .can_be_deleted = can_be_deleted, .initialized = false, }); maybe_finalize_environment_shape(vm); increment_environment_serial_number(); // 3. Return unused. return {}; } // 9.1.1.1.3 CreateImmutableBinding ( N, S ), https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s ThrowCompletionOr DeclarativeEnvironment::create_immutable_binding(VM& vm, Utf16FlyString const& name, bool strict) { // 1. Assert: envRec does not already have a binding for N. // NOTE: We skip this to avoid O(n) traversal of m_binding_names. // 2. Create an immutable binding in envRec for N and record that it is uninitialized. If S is true, record that the newly created binding is a strict binding. append_binding(Binding { .name = name, .value = {}, .strict = strict, .mutable_ = false, .can_be_deleted = false, .initialized = false, }); maybe_finalize_environment_shape(vm); increment_environment_serial_number(); // 3. Return unused. return {}; } // 9.1.1.1.4 InitializeBinding ( N, V ), https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v // 4.1.1.1.1 InitializeBinding ( N, V, hint ), https://tc39.es/proposal-explicit-resource-management/#sec-declarative-environment-records ThrowCompletionOr DeclarativeEnvironment::initialize_binding(VM& vm, Utf16FlyString const& name, Value value, Environment::InitializeBindingHint hint) { return initialize_binding_direct(vm, find_binding_and_index(name)->index().value(), value, hint); } ThrowCompletionOr DeclarativeEnvironment::initialize_binding_direct(VM& vm, size_t index, Value value, Environment::InitializeBindingHint hint) { // 1. Assert: envRec must have an uninitialized binding for N. VERIFY(!binding_is_initialized(index)); VERIFY(!value.is_special_empty_value()); // 2. If hint is not normal, perform ? AddDisposableResource(envRec.[[DisposeCapability]], V, hint). if (hint != Environment::InitializeBindingHint::Normal) TRY(add_disposable_resource(vm, ensure_rare_data().m_dispose_capability, value, hint)); // 3. Set the bound value for N in envRec to V. m_binding_values[index] = value; // 5. Return unused. return {}; } // 9.1.1.1.5 SetMutableBinding ( N, V, S ), https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s ThrowCompletionOr DeclarativeEnvironment::set_mutable_binding(VM& vm, Utf16FlyString const& name, Value value, bool strict) { // 1. If envRec does not have a binding for N, then auto binding_and_index = find_binding_and_index(name); if (!binding_and_index.has_value()) { // a. If S is true, throw a ReferenceError exception. if (strict) return vm.throw_completion(ErrorType::UnknownIdentifier, name); // b. Perform ! envRec.CreateMutableBinding(N, true). MUST(create_mutable_binding(vm, name, true)); // c. Perform ! envRec.InitializeBinding(N, V, normal). MUST(initialize_binding(vm, name, value, Environment::InitializeBindingHint::Normal)); // d. Return unused. return {}; } // 2-5. (extracted into a non-standard function below) if (binding_and_index->index().has_value()) { TRY(set_mutable_binding_direct(vm, *binding_and_index->index(), value, strict)); } else { auto binding = binding_and_index->binding(); TRY(set_mutable_binding_direct(vm, binding, value, strict)); } // 6. Return unused. return {}; } ThrowCompletionOr DeclarativeEnvironment::set_mutable_binding_direct(VM& vm, size_t index, Value value, bool strict) { if (binding_is_strict(index)) strict = true; if (!binding_is_initialized(index)) return vm.throw_completion(ErrorType::BindingNotInitialized, binding_name(index)); if (binding_is_mutable(index)) { m_binding_values[index] = value; } else { if (strict) return vm.throw_completion(ErrorType::InvalidAssignToConst); } return {}; } ThrowCompletionOr DeclarativeEnvironment::set_mutable_binding_direct(VM& vm, Binding& binding, Value value, bool strict) { if (binding.strict) strict = true; if (!binding.initialized) return vm.throw_completion(ErrorType::BindingNotInitialized, binding.name); if (binding.mutable_) { binding.value = value; } else { if (strict) return vm.throw_completion(ErrorType::InvalidAssignToConst); } return {}; } // 9.1.1.1.6 GetBindingValue ( N, S ), https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s ThrowCompletionOr DeclarativeEnvironment::get_binding_value(VM& vm, Utf16FlyString const& name, [[maybe_unused]] bool strict) { // 1. Assert: envRec has a binding for N. auto binding_and_index = find_binding_and_index(name); VERIFY(binding_and_index.has_value()); // 2-3. (extracted into a non-standard function below) if (binding_and_index->index().has_value()) return get_binding_value_direct(vm, *binding_and_index->index()); auto binding = binding_and_index->binding(); return get_binding_value_direct(vm, binding); } // 9.1.1.1.7 DeleteBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-deletebinding-n ThrowCompletionOr DeclarativeEnvironment::delete_binding(VM&, Utf16FlyString const& name) { // 1. Assert: envRec has a binding for the name that is the value of N. auto binding_and_index = find_binding_and_index(name); VERIFY(binding_and_index.has_value()); // 2. If the binding for N in envRec cannot be deleted, return false. if (!binding_and_index->index().has_value()) { if (!binding_and_index->binding().can_be_deleted) return false; VERIFY_NOT_REACHED(); } auto index = *binding_and_index->index(); if (!binding_can_be_deleted(index)) return false; // 3. Remove the binding for N from envRec. // NOTE: We keep the entry in the parallel vectors to avoid disturbing indices. clear_binding(name, index); increment_environment_serial_number(); // 4. Return true. return true; } ThrowCompletionOr DeclarativeEnvironment::initialize_or_set_mutable_binding(VM& vm, Utf16FlyString const& name, Value value) { auto binding_and_index = find_binding_and_index(name); VERIFY(binding_and_index.has_value()); VERIFY(binding_and_index->index().has_value()); auto index = *binding_and_index->index(); if (!binding_is_initialized(index)) TRY(initialize_binding_direct(vm, index, value, Environment::InitializeBindingHint::Normal)); else TRY(set_mutable_binding_direct(vm, index, value, false)); return {}; } void DeclarativeEnvironment::shrink_to_fit() { m_binding_values.shrink_to_fit(); if (!m_rare_data) return; m_rare_data->m_binding_names.shrink_to_fit(); m_rare_data->m_binding_flags.shrink_to_fit(); if (m_binding_values.is_empty()) { m_rare_data->m_deleted_bindings = {}; drop_rare_data_if_empty(); return; } if (m_rare_data->m_deleted_bindings.is_null()) { drop_rare_data_if_empty(); return; } auto deleted_bindings = MUST(Bitmap::create(m_binding_values.size(), false)); for (size_t i = 0; i < m_binding_values.size(); ++i) deleted_bindings.set(i, binding_is_deleted(i)); m_rare_data->m_deleted_bindings = move(deleted_bindings); drop_rare_data_if_empty(); } }