/* * Copyright (c) 2020-2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace JS { class JS_API DeclarativeEnvironment : public Environment { JS_ENVIRONMENT(DeclarativeEnvironment, Environment); GC_DECLARE_ALLOCATOR(DeclarativeEnvironment); struct Binding { Utf16FlyString name; Value value; bool strict { false }; bool mutable_ { false }; bool can_be_deleted { false }; bool initialized { false }; }; public: virtual ~DeclarativeEnvironment() override = default; virtual bool is_catch_environment() const override; void set_is_catch_environment(bool); virtual ThrowCompletionOr has_binding(Utf16FlyString const& name, Optional* = nullptr) const override final; virtual ThrowCompletionOr create_mutable_binding(VM&, Utf16FlyString const& name, bool can_be_deleted) override final; virtual ThrowCompletionOr create_immutable_binding(VM&, Utf16FlyString const& name, bool strict) override final; virtual ThrowCompletionOr initialize_binding(VM&, Utf16FlyString const& name, Value, InitializeBindingHint) override final; virtual ThrowCompletionOr set_mutable_binding(VM&, Utf16FlyString const& name, Value, bool strict) override final; virtual ThrowCompletionOr get_binding_value(VM&, Utf16FlyString const& name, bool strict) override; virtual ThrowCompletionOr delete_binding(VM&, Utf16FlyString const& name) override; ThrowCompletionOr initialize_or_set_mutable_binding(VM&, Utf16FlyString const& name, Value value); // This is not a method defined in the spec! Do not use this in any LibJS (or other spec related) code. [[nodiscard]] Vector bindings() const { Vector names; names.ensure_capacity(binding_count()); auto shape_bindings_to_visit = shape_binding_count(); if (shape_bindings_to_visit > binding_count()) shape_bindings_to_visit = binding_count(); for (size_t i = 0; i < shape_bindings_to_visit; ++i) { auto const& name = m_shape->binding_name(i); if (!binding_is_deleted(i) && !name.is_empty()) names.unchecked_append(name); } if (m_rare_data) { for (auto const& name : m_rare_data->m_binding_names) { if (!name.is_empty()) names.unchecked_append(name); } } return names; } ThrowCompletionOr initialize_binding_direct(VM&, size_t index, Value, InitializeBindingHint); ThrowCompletionOr set_mutable_binding_direct(VM&, size_t index, Value, bool strict); ThrowCompletionOr get_binding_value_direct(VM&, size_t index) const; Value get_initialized_binding_value_direct(size_t index) const { return m_binding_values[index]; } void shrink_to_fit(); void set_environment_shape_cache(GC::Ptr&, size_t expected_binding_count); void ensure_capacity(size_t needed_capacity) { m_binding_values.ensure_capacity(needed_capacity); if (m_shape || needed_capacity == 0) return; auto& rare_data = ensure_rare_data(); rare_data.m_binding_names.ensure_capacity(needed_capacity); rare_data.m_binding_flags.ensure_capacity(needed_capacity); } [[nodiscard]] u64 environment_serial_number() const; DisposeCapability const& dispose_capability() const; DisposeCapability& dispose_capability(); DisposeCapability* dispose_capability_if_exists(); private: struct RareData { void visit_edges(Visitor&) const; [[nodiscard]] size_t external_memory_size() const; [[nodiscard]] bool is_empty() const; Vector m_binding_names; Vector m_binding_flags; Bitmap m_deleted_bindings; HashMap m_bindings_assoc; DisposeCapability m_dispose_capability; GC::Ptr* m_environment_shape_cache { nullptr }; size_t m_expected_binding_count { 0 }; u64 m_environment_serial_number { 0 }; bool m_is_catch_environment { false }; }; static constexpr u8 BindingFlagStrict = EnvironmentShape::BindingFlagStrict; static constexpr u8 BindingFlagMutable = EnvironmentShape::BindingFlagMutable; static constexpr u8 BindingFlagCanBeDeleted = EnvironmentShape::BindingFlagCanBeDeleted; [[nodiscard]] size_t shape_binding_count() const { return m_shape ? m_shape->size() : 0; } [[nodiscard]] size_t binding_count() const { return m_binding_values.size(); } [[nodiscard]] size_t local_binding_index(size_t index) const { return index - shape_binding_count(); } void append_binding(Binding); void clear_binding(Utf16FlyString const& name, size_t index); void ensure_deleted_bindings_capacity(size_t needed_capacity) { auto& rare_data = ensure_rare_data(); if (needed_capacity <= rare_data.m_deleted_bindings.size()) return; rare_data.m_deleted_bindings.grow(ceil_div(needed_capacity, static_cast(8)) * 8, false); } RareData& ensure_rare_data(); void drop_rare_data_if_empty(); void increment_environment_serial_number(); void maybe_finalize_environment_shape(VM&); void set_environment_shape(GC::Ref); Binding binding_at(size_t index) const; Utf16FlyString const& binding_name(size_t index) const { if (index < shape_binding_count()) return m_shape->binding_name(index); VERIFY(m_rare_data); return m_rare_data->m_binding_names[local_binding_index(index)]; } u8 binding_flags(size_t index) const { if (index < shape_binding_count()) return m_shape->binding_flags(index); VERIFY(m_rare_data); return m_rare_data->m_binding_flags[local_binding_index(index)]; } bool binding_is_strict(size_t index) const { return (binding_flags(index) & BindingFlagStrict) != 0; } bool binding_is_mutable(size_t index) const { return (binding_flags(index) & BindingFlagMutable) != 0; } bool binding_can_be_deleted(size_t index) const { return (binding_flags(index) & BindingFlagCanBeDeleted) != 0; } bool binding_is_initialized(size_t index) const { return !m_binding_values[index].is_special_empty_value(); } bool binding_is_deleted(size_t index) const { return m_rare_data && !m_rare_data->m_deleted_bindings.is_null() && m_rare_data->m_deleted_bindings.get(index); } ThrowCompletionOr get_binding_value_direct(VM&, Binding const&) const; ThrowCompletionOr set_mutable_binding_direct(VM&, Binding&, Value, bool strict); protected: DeclarativeEnvironment(); explicit DeclarativeEnvironment(Environment* parent_environment); DeclarativeEnvironment(Environment* parent_environment, ReadonlySpan bindings); virtual void visit_edges(Visitor&) override; virtual size_t external_memory_size() const override; class BindingAndIndex { public: Binding binding() const { if (m_referenced_environment) return m_referenced_environment->binding_at(m_index.value()); return m_temporary_binding; } BindingAndIndex(DeclarativeEnvironment const& environment, size_t index) : m_referenced_environment(&environment) , m_index(index) { } explicit BindingAndIndex(Binding temporary_binding) : m_temporary_binding(move(temporary_binding)) { } Optional const& index() const { return m_index; } private: GC::Ptr m_referenced_environment; Binding m_temporary_binding {}; Optional m_index; }; friend class ModuleEnvironment; virtual Optional find_binding_and_index(Utf16FlyString const& name) const { if (m_shape) { auto index = m_shape->find_binding(name); if (index.has_value() && *index < binding_count() && !binding_is_deleted(*index)) return BindingAndIndex { *this, *index }; } if (m_rare_data) { if (auto it = m_rare_data->m_bindings_assoc.find(name); it != m_rare_data->m_bindings_assoc.end()) return BindingAndIndex { *this, it->value }; } return {}; } private: GC::Ptr m_shape; Vector m_binding_values; OwnPtr m_rare_data; }; inline ThrowCompletionOr DeclarativeEnvironment::get_binding_value_direct(VM& vm, size_t index) const { if (!binding_is_initialized(index)) return vm.throw_completion(ErrorType::BindingNotInitialized, binding_name(index)); return m_binding_values[index]; } inline ThrowCompletionOr DeclarativeEnvironment::get_binding_value_direct(VM&, Binding const& binding) const { // 2. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. if (!binding.initialized) return vm().throw_completion(ErrorType::BindingNotInitialized, binding.name); // 3. Return the value currently bound to N in envRec. return binding.value; } template<> inline bool Environment::fast_is() const { return is_declarative_environment(); } }