@auto_js/v8 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CMakeLists.txt CHANGED
@@ -39,10 +39,12 @@ target_sources(${v8_js}
39
39
  support/lock.h.cc
40
40
  support/unmaybe.cc
41
41
  transfer/accept.h.cc
42
+ transfer/transfer_list.cc
42
43
  transfer/visit.cc
43
44
  value/array_buffer.h.cc
44
45
  value/array.h.cc
45
46
  value/callback_info.cc
47
+ value/external.h.cc
46
48
  value/fixed_array.h.cc
47
49
  value/function.cc
48
50
  value/function.h.cc
package/_module.cc CHANGED
@@ -11,6 +11,7 @@ export import :handle.value;
11
11
  export import :hash;
12
12
  export import :lock;
13
13
  export import :remote;
14
+ export import :transfer_list;
14
15
  export import :unmaybe;
15
16
  export import :value;
16
17
  export import :value.tag;
package/container/hash.cc CHANGED
@@ -4,13 +4,17 @@ import v8;
4
4
 
5
5
  namespace js::iv8 {
6
6
 
7
+ // Physical location of the local handle's value, comparable between unrelated handle types
8
+ export constexpr auto handle_addressof =
9
+ []<class Type>(v8::Local<Type> local) -> void* { return *local; };
10
+
7
11
  // Hash based on the physical location of the local handle's value
8
12
  export struct address_hash : std::hash<void*> {
9
13
  using std::hash<void*>::operator();
10
14
 
11
15
  template <class Type>
12
16
  auto operator()(v8::Local<Type> local) const -> std::size_t {
13
- return (*this)(*local);
17
+ return (*this)(handle_addressof(local));
14
18
  }
15
19
  };
16
20
 
@@ -132,7 +132,9 @@ auto module_record::link(context_lock_witness lock, module_link_record link_reco
132
132
  }
133
133
  return link_record.modules.at(link_record.payload.at(it->second + index));
134
134
  };
135
+ // nb: The initializer only runs on the first pass through the declaration.
135
136
  thread_local auto* linker_ptr = &linker;
137
+ linker_ptr = &linker;
136
138
 
137
139
  // v8 linker callback
138
140
  auto v8_callback = v8::Module::ResolveModuleByIndexCallback{
@@ -1,5 +1,3 @@
1
- module;
2
- #include <v8_js/version.h>
3
1
  export module v8_js:collected_handle;
4
2
  import :lock;
5
3
  import std;
@@ -34,6 +32,7 @@ class collected_handle
34
32
  pool_{pool},
35
33
  value_{std::forward<decltype(args)>(args)...} {}
36
34
 
35
+ auto get() -> Type* { return &value_; }
37
36
  auto operator*() -> Type& { return value_; }
38
37
  static auto make(const collected_handle_lock& lock, auto&&... args) -> unique_ptr
39
38
  requires std::constructible_from<Type, decltype(args)...>;
@@ -45,10 +44,6 @@ class collected_handle
45
44
  Type value_;
46
45
  };
47
46
 
48
- // Untagged v8-managed host object
49
- export template <class Type>
50
- using collected_external_of = collected_handle<v8::External, Type>;
51
-
52
47
  // ---
53
48
 
54
49
  template <class Value, class Type>
@@ -73,27 +68,4 @@ auto collected_handle<Value, Type>::reset(const collected_handle_lock& lock, uni
73
68
  });
74
69
  }
75
70
 
76
- export template <class Type>
77
- auto make_collected_external(const collected_handle_lock& lock, auto&&... args) -> v8::Local<v8::External> {
78
- auto unique = collected_external_of<Type>::make(lock, std::forward<decltype(args)>(args)...);
79
- #if V8_HAS_TAGGED_EXTERNAL
80
- auto value = v8::External::New(lock.isolate(), unique.get(), 0);
81
- #else
82
- auto value = v8::External::New(lock.isolate(), unique.get());
83
- #endif
84
- collected_handle<v8::External, Type>::reset(lock, std::move(unique), value);
85
- return value;
86
- }
87
-
88
- export template <class Type>
89
- auto unwrap_collected_external(v8::Local<v8::External> external) -> Type& {
90
- using handle_type = collected_external_of<Type>;
91
- #if V8_HAS_TAGGED_EXTERNAL
92
- // TODO: Use this feature
93
- return **static_cast<handle_type*>(external->Value(0));
94
- #else
95
- return **static_cast<handle_type*>(external->Value());
96
- #endif
97
- }
98
-
99
71
  } // namespace js::iv8
@@ -16,8 +16,11 @@ class value_of : public value_specialization<Tag>::type {
16
16
  public:
17
17
  using value_specialization<Tag>::type::type;
18
18
 
19
+ template <std::convertible_to<tag_to_v8<Tag>> Type>
19
20
  // NOLINTNEXTLINE(google-explicit-constructor)
20
- operator v8::Local<tag_to_v8<Tag>>() const { return std::bit_cast<v8::Local<tag_to_v8<Tag>>>(*this); }
21
+ operator v8::Local<Type>() const {
22
+ return std::bit_cast<v8::Local<Type>>(v8::Local<v8::Data>{*this});
23
+ }
21
24
  };
22
25
 
23
26
  template <class Type>
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@auto_js/v8",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "author": "https://github.com/laverdet/",
5
5
  "homepage": "https://github.com/laverdet/isolated-vm/tree/experimental/packages/auto_js/v8",
6
6
  "license": "ISC",
7
7
  "dependencies": {
8
- "@auto_js/js": "^0.0.11",
9
- "@auto_js/v8_exports": "^0.0.8"
8
+ "@auto_js/js": "^0.0.12",
9
+ "@auto_js/v8_exports": "^0.0.9"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -28,6 +28,8 @@ constexpr auto make_free_function_with_try_catch =
28
28
  using callback_type = decltype(callback);
29
29
  auto bound_function = util::bind{
30
30
  [](callback_type& callback, Lock lock, const v8::FunctionCallbackInfo<v8::Value>& info) noexcept(Nx) -> void {
31
+ auto disallow_js =
32
+ v8::Isolate::DisallowJavascriptExecutionScope{lock.isolate(), v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE};
31
33
  // NOLINTNEXTLINE(cppcoreguidelines-slicing)
32
34
  auto result = invoke_internal_error_scope(lock, [ & ] -> auto {
33
35
  auto run = util::regular_return{[ & ] -> decltype(auto) {
@@ -61,6 +63,8 @@ constexpr auto make_free_function_noexcept =
61
63
  using callback_type = decltype(callback);
62
64
  auto bound_function = util::bind{
63
65
  [](callback_type& callback, Lock lock, const v8::FunctionCallbackInfo<v8::Value>& /*info*/) noexcept -> void {
66
+ auto disallow_js =
67
+ v8::Isolate::DisallowJavascriptExecutionScope{lock.isolate(), v8::Isolate::DisallowJavascriptExecutionScope::CRASH_ON_FAILURE};
64
68
  auto run = util::regular_return{[ & ] -> decltype(auto) {
65
69
  return callback(lock);
66
70
  }};
@@ -1,7 +1,5 @@
1
- module;
2
- #include <v8_js/version.h>
3
1
  export module v8_js:callback_storage;
4
- import :collected_handle;
2
+ import :external;
5
3
  import :unmaybe;
6
4
  import std;
7
5
  import v8;
@@ -70,41 +68,32 @@ auto make_callback_storage(const auto& lock, auto function) {
70
68
  auto bound = make_bound_callback(std::type_identity<signature_type>{}, lock, std::move(function));
71
69
  using bound_type = decltype(bound);
72
70
  if constexpr (std::is_trivially_copyable_v<bound_type>) {
73
- static_assert(std::is_trivially_destructible_v<bound_type>);
74
71
  if constexpr (std::is_empty_v<bound_type>) {
75
72
  // Constant expression function of 0 size. No data needed at all!
76
73
  auto data = v8::Local<v8::Value>{};
77
- const auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
74
+ constexpr auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
78
75
  auto invoke = bound_type{};
79
76
  invoke(info);
80
77
  }};
81
78
  return std::tuple{callback, data};
82
- } else if constexpr (sizeof(bound_type) == sizeof(void*)) {
83
- // Trivial function of pointer type. Data() is the function data.
84
- auto data = [ & ] -> auto {
85
- #if V8_HAS_TAGGED_EXTERNAL
86
- return v8::External::New(lock.isolate(), std::bit_cast<void*>(bound), 0);
87
- #else
88
- return v8::External::New(lock.isolate(), std::bit_cast<void*>(bound));
89
- #endif
90
- }();
91
- const auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
92
- auto invoke = [ & ] -> auto {
93
- #if V8_HAS_TAGGED_EXTERNAL
94
- // TODO: Use this feature
95
- return std::bit_cast<bound_type>(info.Data().As<v8::External>()->Value(0));
96
- #else
97
- return std::bit_cast<bound_type>(info.Data().As<v8::External>()->Value());
98
- #endif
99
- }();
100
- invoke(info);
101
- }};
102
- return std::tuple{callback, data};
103
79
  } else {
104
- // Trivial data-only function type. `Data()` is a latin1 string containing the agent & function data.
80
+ if constexpr (sizeof(bound_type) <= sizeof(void*)) {
81
+ // Trivial function which can fit inside a pointer. There is a v8 DCHECK on nullptr, so in
82
+ // that case it falls through to the latin1 representation.
83
+ if (auto* pointer = util::bit_padding_cast<void*>(bound); pointer != nullptr) {
84
+ using external_type = untagged_external<void>;
85
+ auto data = v8::Local<v8::Value>{external_type::make(lock, pointer)};
86
+ constexpr auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
87
+ auto invoke = util::bit_truncation_cast<bound_type>(info.Data().As<external_type>()->value());
88
+ invoke(info);
89
+ }};
90
+ return std::tuple{callback, data};
91
+ }
92
+ }
93
+ // Trivial data-only function type. `Data()` is a latin1 string containing the function data.
105
94
  auto data =
106
95
  unmaybe(v8::String::NewFromOneByte(lock.isolate(), reinterpret_cast<const std::uint8_t*>(&bound), v8::NewStringType::kNormal, sizeof(bound)));
107
- const auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
96
+ constexpr auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
108
97
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
109
98
  std::array<std::byte, sizeof(bound_type)> data;
110
99
  auto* isolate = info.GetIsolate();
@@ -112,13 +101,14 @@ auto make_callback_storage(const auto& lock, auto function) {
112
101
  auto invoke = std::bit_cast<bound_type>(data);
113
102
  invoke(info);
114
103
  }};
115
- return std::tuple{callback, data};
104
+ return std::tuple{callback, v8::Local<v8::Value>{data}};
116
105
  }
117
106
  } else {
118
- // Otherwise the function requires bound state w/ a destructor
119
- auto data = make_collected_external<bound_type>(lock, std::move(bound));
120
- const auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
121
- auto& invoke = unwrap_collected_external<bound_type>(info.Data().As<v8::External>());
107
+ // Otherwise `Data()` is a collected external.
108
+ using external_type = untagged_external<bound_type>;
109
+ auto data = external_type::make_collected(lock, std::move(bound));
110
+ constexpr auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
111
+ auto& invoke = *info.Data().As<external_type>()->value();
122
112
  invoke(info);
123
113
  }};
124
114
  return std::tuple{callback, data};
@@ -116,14 +116,24 @@ auto accept_v8_value::operator()(function_prototype_tag /*tag*/, visit_holder /*
116
116
  auto accept_v8_value::operator()(array_buffer_tag /*tag*/, visit_holder /*visit*/, js::array_buffer subject) const
117
117
  -> js::referenceable_value<v8::Local<v8::ArrayBuffer>> {
118
118
  auto byte_length = subject.byte_length();
119
- auto backing_store = v8::ArrayBuffer::NewBackingStore(
120
- std::move(subject).acquire_ownership().release(),
121
- byte_length,
122
- [](void* data, std::size_t /*length*/, void* /*deleter_data*/) -> void {
123
- std::ignore = js::array_buffer::unique_pointer_type{reinterpret_cast<std::byte*>(data)};
124
- },
125
- nullptr
126
- );
119
+ auto backing_store = [ & ] -> auto {
120
+ // nb: v8 does not call the deleter when `byte_length` is zero.
121
+ if (byte_length == 0) {
122
+ return v8::ArrayBuffer::NewBackingStore(nullptr, 0, nullptr, nullptr);
123
+ } else {
124
+ auto holder = std::make_unique<js::array_buffer>(std::move(subject));
125
+ auto backing_store = v8::ArrayBuffer::NewBackingStore(
126
+ holder->data(),
127
+ byte_length,
128
+ [](void* /*data*/, std::size_t /*length*/, void* param) -> void {
129
+ delete static_cast<js::array_buffer*>(param);
130
+ },
131
+ holder.get()
132
+ );
133
+ std::ignore = holder.release();
134
+ return backing_store;
135
+ }
136
+ }();
127
137
  auto value = v8::ArrayBuffer::New(isolate(), std::move(backing_store));
128
138
  return js::referenceable_value{value};
129
139
  }
@@ -0,0 +1,141 @@
1
+ export module v8_js:transfer_list;
2
+ import :array;
3
+ import :handle.value;
4
+ import :object;
5
+ import :lock;
6
+ import :unmaybe;
7
+ import :value.tag;
8
+ import auto_js;
9
+ import std;
10
+ import util;
11
+ import v8;
12
+
13
+ namespace js::iv8 {
14
+
15
+ // Transfer delegate for `ArrayBuffer`
16
+ export class array_buffer_transfer {
17
+ public:
18
+ array_buffer_transfer(context_lock_witness /*lock*/, std::vector<v8::Local<v8::Value>>& entries) :
19
+ buffers_{[ & ] -> auto {
20
+ constexpr auto maybe_claim_in = [](v8::Local<v8::Value> value) -> std::optional<v8::Local<v8::ArrayBuffer>> {
21
+ if (value->IsArrayBuffer()) {
22
+ auto buffer = value.As<v8::ArrayBuffer>();
23
+ if (!buffer->IsDetachable()) {
24
+ throw js::type_error{u"ArrayBuffer is not detachable"};
25
+ }
26
+ return buffer;
27
+ } else {
28
+ return std::nullopt;
29
+ }
30
+ };
31
+ return js::extract_transferees(util::cw<u"ArrayBuffer">, maybe_claim_in, std::equal_to{}, entries);
32
+ }()} {}
33
+
34
+ template <class Visit, class Accept>
35
+ auto operator()(v8::Local<v8::ArrayBuffer> subject, Visit& visit, const Accept& accept) {
36
+ return maybe_claim_out(subject, visit, accept);
37
+ }
38
+
39
+ template <class Visit, class Accept>
40
+ auto operator()(v8::Local<iv8::DataBlock> subject, Visit& visit, const Accept& accept) {
41
+ return maybe_claim_out(subject, visit, accept);
42
+ }
43
+
44
+ auto finalize() -> void {
45
+ for (auto entry : std::exchange(buffers_, {})) {
46
+ unmaybe(entry->Detach(v8::Local<v8::Value>{}));
47
+ }
48
+ }
49
+
50
+ private:
51
+ template <class Visit, class Accept>
52
+ auto maybe_claim_out(auto subject, Visit& visit, const Accept& accept) {
53
+ auto claim_out = [ & ](v8::Local<v8::ArrayBuffer> handle) -> accept_target_t<Accept> {
54
+ auto buffer = [ & ] -> js::array_buffer {
55
+ if (handle->ByteLength() == 0) {
56
+ return js::array_buffer{std::span<std::byte>{}};
57
+ } else {
58
+ // Steal the backing store, avoiding a copy of the contents
59
+ auto backing_store = std::make_unique<std::shared_ptr<v8::BackingStore>>(handle->GetBackingStore());
60
+ auto data = std::span{static_cast<std::byte*>((*backing_store)->Data()), (*backing_store)->ByteLength()};
61
+ return js::array_buffer{
62
+ data,
63
+ js::array_buffer::deleter{
64
+ [](std::shared_ptr<v8::BackingStore>* backing_store, std::byte* /*data*/) -> void {
65
+ delete backing_store;
66
+ },
67
+ backing_store.release()
68
+ }
69
+ };
70
+ }
71
+ }();
72
+ unmaybe(handle->Detach(v8::Local<v8::Value>{}));
73
+ return accept(array_buffer_tag{}, visit, js::transferred_value{v8::Local<v8::Data>{handle}, buffer});
74
+ };
75
+ return js::claim_transferee(buffers_, subject, std::equal_to{}, claim_out);
76
+ }
77
+
78
+ std::vector<v8::Local<v8::ArrayBuffer>> buffers_;
79
+ };
80
+
81
+ // v8 'transferList' delegate
82
+ export template <class... Delegates>
83
+ class transfer_list {
84
+ public:
85
+ template <class Type>
86
+ transfer_list(context_lock_witness lock, std::optional<Type> list) :
87
+ transfer_list{lock, list ? entries_of(lock, list->template As<v8::Array>()) : std::vector<v8::Local<v8::Value>>{}} {}
88
+
89
+ transfer_list(context_lock_witness lock, value_of<list_tag> list) :
90
+ transfer_list{lock, entries_of(lock, list.As<v8::Array>())} {}
91
+
92
+ // Constructs a list in place and invokes the given operation with it, finalizing afterwards
93
+ static auto with(context_lock_witness lock, auto list, auto operation) {
94
+ auto self = transfer_list{lock, std::move(list)};
95
+ auto result = operation(self);
96
+ self.finalize();
97
+ return result;
98
+ }
99
+
100
+ // Offers the subject to each delegate in order, claiming with the first which engages
101
+ template <class Visit, class Accept>
102
+ auto operator()(auto subject, Visit& visit, const Accept& accept) -> std::optional<accept_target_t<Accept>> {
103
+ return util::template_traverse(
104
+ util::sequence_cw<sizeof...(Delegates)>,
105
+ util::overloaded{
106
+ [ & ](auto ii, auto next) -> std::optional<accept_target_t<Accept>> {
107
+ auto& delegate = std::get<ii>(delegates_);
108
+ if constexpr (std::invocable<decltype(delegate), decltype(subject), Visit&, const Accept&>) {
109
+ if (auto claim = delegate(subject, visit, accept)) {
110
+ return std::optional{(*std::move(claim))()};
111
+ }
112
+ }
113
+ return next();
114
+ },
115
+ [] -> std::optional<accept_target_t<Accept>> { return std::nullopt; },
116
+ }
117
+ );
118
+ }
119
+
120
+ auto finalize() -> void {
121
+ constexpr auto [... ii ] = util::sequence<sizeof...(Delegates)>;
122
+ (..., std::get<ii>(delegates_).finalize());
123
+ }
124
+
125
+ private:
126
+ // Each delegate claims its entries from the vector. Anything left over is unknown.
127
+ transfer_list(context_lock_witness lock, std::vector<v8::Local<v8::Value>> entries) :
128
+ delegates_{Delegates{lock, entries}...} {
129
+ if (!entries.empty()) {
130
+ throw js::type_error{u"Transfer list contains unknown value"};
131
+ }
132
+ }
133
+
134
+ static auto entries_of(context_lock_witness lock, v8::Local<v8::Array> list) -> std::vector<v8::Local<v8::Value>> {
135
+ return std::vector{std::from_range, value_for_array{lock, list}};
136
+ }
137
+
138
+ std::tuple<Delegates...> delegates_;
139
+ };
140
+
141
+ } // namespace js::iv8
package/transfer/visit.cc CHANGED
@@ -77,11 +77,11 @@ struct visit_cached_immediate : Visit {
77
77
  using Visit::Visit;
78
78
 
79
79
  template <class Accept>
80
- auto operator()(auto subject, const Accept& accept) -> accept_target_t<Accept>
80
+ auto operator()(this auto& self, auto subject, const Accept& accept) -> accept_target_t<Accept>
81
81
  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124954
82
- requires requires { this->immediate(subject, accept); } {
83
- return lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
84
- return immediate(subject, accept);
82
+ requires requires { self.immediate(subject, accept); } {
83
+ return self.lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
84
+ return self.immediate(subject, accept);
85
85
  });
86
86
  }
87
87
  };
@@ -94,10 +94,10 @@ struct visit_uncached_immediate : Visit {
94
94
  using Visit::Visit;
95
95
 
96
96
  template <class Accept>
97
- auto operator()(auto subject, const Accept& accept) -> accept_target_t<Accept>
97
+ auto operator()(this auto& self, auto subject, const Accept& accept) -> accept_target_t<Accept>
98
98
  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124954
99
- requires requires { this->immediate(subject, accept); } {
100
- return immediate(subject, accept);
99
+ requires requires { self.immediate(subject, accept); } {
100
+ return self.immediate(subject, accept);
101
101
  }
102
102
  };
103
103
 
@@ -121,18 +121,18 @@ struct visit_flat_value : reference_map_t<Reference, visit_reference_map_type> {
121
121
 
122
122
  // numbers
123
123
  template <class Accept>
124
- auto operator()(v8::Local<v8::Number> subject, const Accept& accept) -> accept_target_t<Accept> {
124
+ auto operator()(this auto& self, v8::Local<v8::Number> subject, const Accept& accept) -> accept_target_t<Accept> {
125
125
  if (subject->IsInt32()) {
126
- return immediate(subject.As<v8::Int32>(), accept);
126
+ return self.immediate(subject.As<v8::Int32>(), accept);
127
127
  } else {
128
- return immediate(subject.As<iv8::Double>(), accept);
128
+ return self.immediate(subject.As<iv8::Double>(), accept);
129
129
  }
130
130
  }
131
131
 
132
132
  // boolean
133
133
  template <class Accept>
134
- auto operator()(v8::Local<v8::Boolean> subject, const Accept& accept) -> accept_target_t<Accept> {
135
- return immediate(subject, accept);
134
+ auto operator()(this auto& self, v8::Local<v8::Boolean> subject, const Accept& accept) -> accept_target_t<Accept> {
135
+ return self.immediate(subject, accept);
136
136
  }
137
137
 
138
138
  // extras
@@ -142,19 +142,19 @@ struct visit_flat_value : reference_map_t<Reference, visit_reference_map_type> {
142
142
  protected:
143
143
  // primitives
144
144
  template <class Accept>
145
- auto immediate(v8::Local<v8::Primitive> subject, const Accept& accept) -> accept_target_t<Accept> {
145
+ auto immediate(this auto& self, v8::Local<v8::Primitive> subject, const Accept& accept) -> accept_target_t<Accept> {
146
146
  if (subject->IsUndefined()) {
147
- return accept(undefined_tag{}, *this, subject);
147
+ return accept(undefined_tag{}, self, subject);
148
148
  } else if (subject->IsNull()) {
149
- return accept(null_tag{}, *this, subject);
149
+ return accept(null_tag{}, self, subject);
150
150
  } else if (subject->IsNumber()) {
151
- return (*this)(subject.As<v8::Number>(), accept);
151
+ return self(subject.As<v8::Number>(), accept);
152
152
  } else if (subject->IsName()) {
153
- return immediate(subject.As<v8::Name>(), accept);
153
+ return self.immediate(subject.As<v8::Name>(), accept);
154
154
  } else if (subject->IsBoolean()) {
155
- return (*this)(subject.As<v8::Boolean>(), accept);
155
+ return self(subject.As<v8::Boolean>(), accept);
156
156
  } else if (subject->IsBigInt()) {
157
- return immediate(subject.As<v8::BigInt>(), accept);
157
+ return self.immediate(subject.As<v8::BigInt>(), accept);
158
158
  } else {
159
159
  std::unreachable();
160
160
  }
@@ -162,86 +162,86 @@ struct visit_flat_value : reference_map_t<Reference, visit_reference_map_type> {
162
162
 
163
163
  template <class Accept, class Type>
164
164
  requires std::is_convertible_v<Type, v8::Primitive>
165
- auto immediate(v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
166
- return accept_tagged(subject, accept);
165
+ auto immediate(this auto& self, v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
166
+ return self.accept_tagged(subject, accept);
167
167
  }
168
168
 
169
169
  // names
170
170
  template <class Accept>
171
- auto immediate(v8::Local<v8::Name> subject, const Accept& accept) -> accept_target_t<Accept> {
171
+ auto immediate(this auto& self, v8::Local<v8::Name> subject, const Accept& accept) -> accept_target_t<Accept> {
172
172
  if (subject->IsString()) {
173
- return immediate(subject.As<v8::String>(), accept);
173
+ return self.immediate(subject.As<v8::String>(), accept);
174
174
  } else {
175
- return immediate(subject.As<v8::Symbol>(), accept);
175
+ return self.immediate(subject.As<v8::Symbol>(), accept);
176
176
  }
177
177
  }
178
178
 
179
179
  template <class Accept>
180
- auto immediate(v8::Local<v8::String> subject, const Accept& accept) -> accept_target_t<Accept> {
180
+ auto immediate(this auto& self, v8::Local<v8::String> subject, const Accept& accept) -> accept_target_t<Accept> {
181
181
  if (subject->IsOneByte()) {
182
- return immediate(subject.As<iv8::StringOneByte>(), accept);
182
+ return self.immediate(subject.As<iv8::StringOneByte>(), accept);
183
183
  } else {
184
- return immediate(subject.As<iv8::StringTwoByte>(), accept);
184
+ return self.immediate(subject.As<iv8::StringTwoByte>(), accept);
185
185
  }
186
186
  }
187
187
 
188
188
  // bigint
189
189
  template <class Accept>
190
- auto immediate(v8::Local<v8::BigInt> subject, const Accept& accept) -> accept_target_t<Accept> {
190
+ auto immediate(this auto& self, v8::Local<v8::BigInt> subject, const Accept& accept) -> accept_target_t<Accept> {
191
191
  bool lossless{};
192
192
  auto i64 = subject->Int64Value(&lossless);
193
193
  if (lossless) {
194
- return accept(bigint_tag_of<std::int64_t>{}, *this, value_of{witness(), subject.As<iv8::BigInt64>(), i64});
194
+ return accept(bigint_tag_of<std::int64_t>{}, self, value_of{self.witness(), subject.As<iv8::BigInt64>(), i64});
195
195
  } else {
196
- return immediate(subject.As<iv8::BigIntWords>(), accept);
196
+ return self.immediate(subject.As<iv8::BigIntWords>(), accept);
197
197
  }
198
198
  }
199
199
 
200
200
  // date
201
201
  template <class Accept>
202
- auto immediate(v8::Local<v8::Date> subject, const Accept& accept) -> accept_target_t<Accept> {
203
- return accept_tagged(subject, accept);
202
+ auto immediate(this auto& self, v8::Local<v8::Date> subject, const Accept& accept) -> accept_target_t<Accept> {
203
+ return self.accept_tagged(subject, accept);
204
204
  }
205
205
 
206
206
  // data blocks
207
207
  template <class Accept>
208
- auto immediate(v8::Local<iv8::DataBlock> subject, const Accept& accept) -> accept_target_t<Accept> {
208
+ auto immediate(this auto& self, v8::Local<iv8::DataBlock> subject, const Accept& accept) -> accept_target_t<Accept> {
209
209
  if (subject->IsSharedArrayBuffer()) {
210
- return immediate(subject.As<v8::SharedArrayBuffer>(), accept);
210
+ return self.immediate(subject.As<v8::SharedArrayBuffer>(), accept);
211
211
  } else {
212
- return immediate(subject.As<v8::ArrayBuffer>(), accept);
212
+ return self.immediate(subject.As<v8::ArrayBuffer>(), accept);
213
213
  }
214
214
  }
215
215
 
216
216
  template <class Accept, class Type>
217
217
  requires std::is_convertible_v<iv8::v8_to_tag<Type>, data_block_tag>
218
- auto immediate(v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
219
- return accept_tagged(subject, accept);
218
+ auto immediate(this auto& self, v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
219
+ return self.accept_tagged(subject, accept);
220
220
  }
221
221
 
222
222
  // external
223
223
  template <class Accept>
224
- auto immediate(v8::Local<v8::External> subject, const Accept& accept) -> accept_target_t<Accept> {
225
- return accept_tagged(subject, accept);
224
+ auto immediate(this auto& self, v8::Local<v8::External> subject, const Accept& accept) -> accept_target_t<Accept> {
225
+ return self.accept_tagged(subject, accept);
226
226
  }
227
227
 
228
228
  // promise (maybe could be forwarded)
229
229
  template <class Accept>
230
- auto immediate(v8::Local<v8::Promise> subject, const Accept& accept) -> accept_target_t<Accept> {
231
- return accept(promise_tag{}, *this, subject);
230
+ auto immediate(this auto& self, v8::Local<v8::Promise> subject, const Accept& accept) -> accept_target_t<Accept> {
231
+ return accept(promise_tag{}, self, subject);
232
232
  }
233
233
 
234
234
  // function (can be forwarded)
235
235
  template <class Type, class Accept>
236
236
  requires std::is_convertible_v<Type, v8::Function>
237
- auto immediate(v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
238
- return accept(function_tag{}, *this, subject);
237
+ auto immediate(this auto& self, v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
238
+ return accept(function_tag{}, self, subject);
239
239
  }
240
240
 
241
241
  // Convenience function which wraps in `iv8::value_of` and invokes `accept`.
242
242
  template <class Type, class Accept>
243
- [[nodiscard]] auto accept_tagged(v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
244
- return accept(iv8::v8_to_tag<Type>{}, *this, value_of{witness(), subject});
243
+ [[nodiscard]] auto accept_tagged(this auto& self, v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
244
+ return accept(iv8::v8_to_tag<Type>{}, self, value_of{self.witness(), subject});
245
245
  }
246
246
 
247
247
  private:
@@ -273,40 +273,40 @@ struct visit_value : visit_flat_value<Target> {
273
273
  visit_value{transfer, context_lock_witness{util::slice(lock)}} {}
274
274
 
275
275
  template <class Accept>
276
- auto operator()(v8::Local<v8::Value> subject, const Accept& accept) -> accept_target_t<Accept> {
276
+ auto operator()(this auto& self, v8::Local<v8::Value> subject, const Accept& accept) -> accept_target_t<Accept> {
277
277
 
278
278
  // Check the reference map, and check type
279
- return lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
279
+ return self.lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
280
280
  return util::template_traverse(
281
281
  accept_tags_of_v<Accept>,
282
282
  util::overloaded{
283
283
  // Fast paths
284
284
  [ & ](undefined_tag /*tag*/, auto next) -> accept_target_t<Accept> {
285
- return subject->IsUndefined() ? accept(undefined_tag{}, *this, subject.As<iv8::Undefined>()) : next();
285
+ return subject->IsUndefined() ? accept(undefined_tag{}, self, subject.As<iv8::Undefined>()) : next();
286
286
  },
287
287
  [ & ](null_tag /*tag*/, auto next) -> accept_target_t<Accept> {
288
- return subject->IsNull() ? accept(null_tag{}, *this, subject.As<iv8::Null>()) : next();
288
+ return subject->IsNull() ? accept(null_tag{}, self, subject.As<iv8::Null>()) : next();
289
289
  },
290
290
  [ & ](boolean_tag /*tag*/, auto next) -> accept_target_t<Accept> {
291
- return subject->IsBoolean() ? (*this)(subject.As<v8::Boolean>(), accept) : next();
291
+ return subject->IsBoolean() ? self(subject.As<v8::Boolean>(), accept) : next();
292
292
  },
293
293
  [ & ](number_tag /*tag*/, auto next) -> accept_target_t<Accept> {
294
- return subject->IsNumber() ? (*this)(subject.As<v8::Number>(), accept) : next();
294
+ return subject->IsNumber() ? self(subject.As<v8::Number>(), accept) : next();
295
295
  },
296
296
  [ & ](string_tag /*tag*/, auto next) -> accept_target_t<Accept> {
297
- return subject->IsString() ? immediate(subject.As<v8::String>(), accept) : next();
297
+ return subject->IsString() ? self.immediate(subject.As<v8::String>(), accept) : next();
298
298
  },
299
299
  [ & ](bigint_tag /*tag*/, auto next) -> accept_target_t<Accept> {
300
- return subject->IsBigInt() ? immediate(subject.As<v8::BigInt>(), accept) : next();
300
+ return subject->IsBigInt() ? self.immediate(subject.As<v8::BigInt>(), accept) : next();
301
301
  },
302
302
  [ & ](date_tag /*tag*/, auto next) -> accept_target_t<Accept> {
303
- return subject->IsDate() ? immediate(subject.As<v8::Date>(), accept) : next();
303
+ return subject->IsDate() ? self.immediate(subject.As<v8::Date>(), accept) : next();
304
304
  },
305
305
  [ & ](list_tag /*tag*/, auto next) -> accept_target_t<Accept> {
306
- return subject->IsArray() ? immediate(subject.As<v8::Array>(), accept) : next();
306
+ return subject->IsArray() ? self.immediate(subject.As<v8::Array>(), accept) : next();
307
307
  },
308
308
  [ & ](function_tag /*tag*/, auto next) -> accept_target_t<Accept> {
309
- return subject->IsFunction() ? immediate(subject.As<iv8::Function>(), accept) : next();
309
+ return subject->IsFunction() ? self.immediate(subject.As<iv8::Function>(), accept) : next();
310
310
  },
311
311
 
312
312
  // Unknown tag
@@ -315,9 +315,9 @@ struct visit_value : visit_flat_value<Target> {
315
315
  // Slow path
316
316
  [ & ] -> accept_target_t<Accept> {
317
317
  if (subject->IsObject()) {
318
- return immediate(subject.As<v8::Object>(), accept);
318
+ return self.immediate(subject.As<v8::Object>(), accept);
319
319
  } else {
320
- return immediate(subject.As<v8::Primitive>(), accept);
320
+ return self.immediate(subject.As<v8::Primitive>(), accept);
321
321
  }
322
322
  },
323
323
  }
@@ -326,19 +326,19 @@ struct visit_value : visit_flat_value<Target> {
326
326
  }
327
327
 
328
328
  template <class Accept>
329
- auto operator()(v8::Local<iv8::DataBlock> subject, const Accept& accept) -> accept_target_t<Accept> {
330
- return lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
329
+ auto operator()(this auto& self, v8::Local<iv8::DataBlock> subject, const Accept& accept) -> accept_target_t<Accept> {
330
+ return self.lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
331
331
  return util::template_traverse(
332
332
  accept_tags_of_v<Accept>,
333
333
  util::overloaded{
334
334
  // Fast paths
335
- [ & ](data_block_tag /*tag*/, auto /*next*/) -> accept_target_t<Accept> { return accept_tagged(subject, accept); },
335
+ [ & ](data_block_tag /*tag*/, auto /*next*/) -> accept_target_t<Accept> { return self.accept_tagged(subject, accept); },
336
336
  [ & ](array_buffer_tag /*tag*/, auto next) -> accept_target_t<Accept> { return next(); },
337
337
  [ & ](shared_array_buffer_tag /*tag*/, auto next) -> accept_target_t<Accept> { return next(); },
338
338
 
339
339
  // Slow path
340
340
  [ & ] -> accept_target_t<Accept> {
341
- return immediate(subject, accept);
341
+ return self.immediate(subject, accept);
342
342
  }
343
343
  }
344
344
  );
@@ -352,72 +352,72 @@ struct visit_value : visit_flat_value<Target> {
352
352
 
353
353
  protected:
354
354
  // object
355
- template <class Accept>
356
- auto immediate(v8::Local<v8::Object> subject, const Accept& accept) -> accept_target_t<Accept> {
355
+ template <class Visit, class Accept>
356
+ auto immediate(this Visit& self, v8::Local<v8::Object> subject, const Accept& accept) -> accept_target_t<Accept> {
357
357
  if (subject->IsArray()) {
358
- return immediate(subject.As<v8::Array>(), accept);
358
+ return self.immediate(subject.As<v8::Array>(), accept);
359
359
  } else if (subject->IsExternal()) {
360
- return immediate(subject.As<v8::External>(), accept);
360
+ return self.immediate(subject.As<v8::External>(), accept);
361
361
  } else if (subject->IsDate()) {
362
- return immediate(subject.As<v8::Date>(), accept);
362
+ return self.immediate(subject.As<v8::Date>(), accept);
363
363
  } else if (subject->IsArrayBuffer()) {
364
- return immediate(subject.As<v8::ArrayBuffer>(), accept);
364
+ return self.immediate(subject.As<v8::ArrayBuffer>(), accept);
365
365
  } else if (subject->IsSharedArrayBuffer()) {
366
- return immediate(subject.As<v8::SharedArrayBuffer>(), accept);
366
+ return self.immediate(subject.As<v8::SharedArrayBuffer>(), accept);
367
367
  } else if (subject->IsArrayBufferView()) {
368
- return immediate(subject.As<v8::ArrayBufferView>(), accept);
368
+ return self.immediate(subject.As<v8::ArrayBufferView>(), accept);
369
369
  } else if (subject->IsPromise()) {
370
- return immediate(subject.As<v8::Promise>(), accept);
370
+ return self.immediate(subject.As<v8::Promise>(), accept);
371
371
  } else if (subject->IsFunction()) {
372
- return immediate(subject.As<iv8::Function>(), accept);
372
+ return self.immediate(subject.As<iv8::Function>(), accept);
373
373
  } else {
374
- auto visit_entry = visit_entry_pair<visit_property_name<visit_value>, visit_value&>{*this};
375
- return accept(dictionary_tag{}, visit_entry, value_of{witness(), subject.As<v8::Object>()});
374
+ auto visit_entry = visit_entry_pair<visit_property_name<Visit>, Visit&>{self};
375
+ return accept(dictionary_tag{}, visit_entry, value_of{self.witness(), subject.As<v8::Object>()});
376
376
  }
377
377
  }
378
378
 
379
379
  // array
380
- template <class Accept>
381
- auto immediate(v8::Local<v8::Array> subject, const Accept& accept) -> accept_target_t<Accept> {
382
- auto visit_entry = visit_entry_pair<visit_property_name<visit_value>, visit_value&>{*this};
383
- return accept(list_tag{}, visit_entry, value_of{witness(), subject.As<v8::Array>()});
380
+ template <class Visit, class Accept>
381
+ auto immediate(this Visit& self, v8::Local<v8::Array> subject, const Accept& accept) -> accept_target_t<Accept> {
382
+ auto visit_entry = visit_entry_pair<visit_property_name<Visit>, Visit&>{self};
383
+ return accept(list_tag{}, visit_entry, value_of{self.witness(), subject.As<v8::Array>()});
384
384
  }
385
385
 
386
386
  // function template
387
387
  template <class Accept>
388
- auto immediate(v8::Local<v8::FunctionTemplate> subject, const Accept& accept) -> accept_target_t<Accept> {
389
- return accept(function_tag{}, *this, unmaybe(subject->GetFunction(context_)));
388
+ auto immediate(this auto& self, v8::Local<v8::FunctionTemplate> subject, const Accept& accept) -> accept_target_t<Accept> {
389
+ return accept(function_tag{}, self, unmaybe(subject->GetFunction(self.witness().context())));
390
390
  }
391
391
 
392
392
  // array buffer views (typed arrays, data view)
393
393
  template <class Accept>
394
- auto immediate(v8::Local<v8::ArrayBufferView> subject, const Accept& accept) -> accept_target_t<Accept> {
394
+ auto immediate(this auto& self, v8::Local<v8::ArrayBufferView> subject, const Accept& accept) -> accept_target_t<Accept> {
395
395
  if (subject->IsUint8Array()) {
396
- return immediate(subject.As<v8::Uint8Array>(), accept);
396
+ return self.immediate(subject.As<v8::Uint8Array>(), accept);
397
397
  } else if (subject->IsDataView()) {
398
- return immediate(subject.As<v8::DataView>(), accept);
398
+ return self.immediate(subject.As<v8::DataView>(), accept);
399
399
  } else if (subject->IsUint8ClampedArray()) {
400
- return immediate(subject.As<v8::Uint8ClampedArray>(), accept);
400
+ return self.immediate(subject.As<v8::Uint8ClampedArray>(), accept);
401
401
  } else if (subject->IsInt8Array()) {
402
- return immediate(subject.As<v8::Int8Array>(), accept);
402
+ return self.immediate(subject.As<v8::Int8Array>(), accept);
403
403
  } else if (subject->IsUint16Array()) {
404
- return immediate(subject.As<v8::Uint16Array>(), accept);
404
+ return self.immediate(subject.As<v8::Uint16Array>(), accept);
405
405
  } else if (subject->IsInt16Array()) {
406
- return immediate(subject.As<v8::Int16Array>(), accept);
406
+ return self.immediate(subject.As<v8::Int16Array>(), accept);
407
407
  } else if (subject->IsUint32Array()) {
408
- return immediate(subject.As<v8::Uint32Array>(), accept);
408
+ return self.immediate(subject.As<v8::Uint32Array>(), accept);
409
409
  } else if (subject->IsInt32Array()) {
410
- return immediate(subject.As<v8::Int32Array>(), accept);
410
+ return self.immediate(subject.As<v8::Int32Array>(), accept);
411
411
  } else if (subject->IsFloat16Array()) {
412
- return immediate(subject.As<v8::Float16Array>(), accept);
412
+ return self.immediate(subject.As<v8::Float16Array>(), accept);
413
413
  } else if (subject->IsFloat32Array()) {
414
- return immediate(subject.As<v8::Float32Array>(), accept);
414
+ return self.immediate(subject.As<v8::Float32Array>(), accept);
415
415
  } else if (subject->IsFloat64Array()) {
416
- return immediate(subject.As<v8::Float64Array>(), accept);
416
+ return self.immediate(subject.As<v8::Float64Array>(), accept);
417
417
  } else if (subject->IsBigInt64Array()) {
418
- return immediate(subject.As<v8::BigInt64Array>(), accept);
418
+ return self.immediate(subject.As<v8::BigInt64Array>(), accept);
419
419
  } else if (subject->IsBigUint64Array()) {
420
- return immediate(subject.As<v8::BigUint64Array>(), accept);
420
+ return self.immediate(subject.As<v8::BigUint64Array>(), accept);
421
421
  } else {
422
422
  throw js::type_error{u"Received exotic v8 'ArrayBufferView'"};
423
423
  }
@@ -425,14 +425,56 @@ struct visit_value : visit_flat_value<Target> {
425
425
 
426
426
  template <class Accept, class Type>
427
427
  requires std::is_convertible_v<Type, v8::ArrayBufferView>
428
- auto immediate(v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
429
- return accept(v8_to_tag<Type>{}, *this, value_of{witness(), subject});
428
+ auto immediate(this auto& self, v8::Local<Type> subject, const Accept& accept) -> accept_target_t<Accept> {
429
+ return accept(v8_to_tag<Type>{}, self, value_of{self.witness(), subject});
430
430
  }
431
431
 
432
432
  private:
433
433
  v8::Local<v8::Context> context_;
434
434
  };
435
435
 
436
+ // Visitor with transfer delegate. The delegate is offered transferable subjects, claiming the ones
437
+ // listed in its `transferList` before the underlying visitor gets a chance to copy them.
438
+ template <class Target, class Delegate>
439
+ struct visit_value_delegate : visit_value<Target> {
440
+ private:
441
+ using visit_type = visit_value<Target>;
442
+
443
+ public:
444
+ visit_value_delegate(auto* transfer, context_lock_witness lock, Delegate& delegate) :
445
+ visit_type{transfer, lock},
446
+ delegate_{delegate} {}
447
+ visit_value_delegate(auto* transfer, const auto& lock, Delegate& delegate) :
448
+ visit_value_delegate{transfer, context_lock_witness{util::slice(lock)}, delegate} {}
449
+
450
+ using visit_type::operator();
451
+ using visit_type::immediate;
452
+
453
+ template <class Accept>
454
+ auto immediate(this auto& self, v8::Local<v8::ArrayBuffer> subject, const Accept& accept) -> accept_target_t<Accept> {
455
+ return self.claim_or_immediate(subject, accept);
456
+ }
457
+
458
+ template <class Accept>
459
+ auto immediate(this auto& self, v8::Local<iv8::DataBlock> subject, const Accept& accept) -> accept_target_t<Accept> {
460
+ return self.claim_or_immediate(subject, accept);
461
+ }
462
+
463
+ private:
464
+ template <class Accept>
465
+ auto claim_or_immediate(this auto& self, auto subject, const Accept& accept) -> accept_target_t<Accept> {
466
+ if (auto claimed = self.delegate_.get()(subject, self, accept)) {
467
+ return *std::move(claimed);
468
+ }
469
+ return self.visit_value<Target>::immediate(subject, accept);
470
+ }
471
+
472
+ std::reference_wrapper<Delegate> delegate_;
473
+ };
474
+
475
+ template <class Meta, class Delegate>
476
+ using visit_value_delegate_with = visit_cached_immediate<visit_value_delegate<typename Meta::accept_reference_type, Delegate>>;
477
+
436
478
  // Forward `value_of<T>` back to acceptor
437
479
  template <class Tag>
438
480
  struct visit_v8_value_of {
@@ -512,6 +554,21 @@ struct visit<Meta, v8::Local<Type>> : iv8::visit_value_with<Meta> {
512
554
  using iv8::visit_value_with<Meta>::visit_value_with;
513
555
  };
514
556
 
557
+ // `transferee_visit_subject` subject visitor
558
+ template <class Meta, class Type, class Delegate>
559
+ struct visit<Meta, transferee_visit_subject<v8::Local<Type>, Delegate>> : iv8::visit_value_delegate_with<Meta, Delegate> {
560
+ private:
561
+ using visit_type = iv8::visit_value_delegate_with<Meta, Delegate>;
562
+
563
+ public:
564
+ using visit_type::visit_type;
565
+
566
+ template <class Accept>
567
+ auto operator()(const transferee_visit_subject<v8::Local<Type>, Delegate>& subject, const Accept& accept) -> accept_target_t<Accept> {
568
+ return util::invoke_as<visit_type>(*this, *subject, accept);
569
+ }
570
+ };
571
+
515
572
  // value_of<T> visitor
516
573
  template <class Meta, class Tag>
517
574
  struct visit<Meta, iv8::value_of<Tag>> : iv8::visit_v8_value_of<Tag> {
package/value/array.cc CHANGED
@@ -1,15 +1,16 @@
1
1
  module v8_js;
2
+ import auto_js;
2
3
  import util;
3
4
  import v8;
4
5
 
5
6
  namespace js::iv8 {
6
7
 
7
8
  auto value_for_array::begin() const -> iterator {
8
- return iterator{util::slice(*this), context(), 0};
9
+ return iterator{util::slice(*this), context(), 0, maybe_sparse_};
9
10
  }
10
11
 
11
12
  auto value_for_array::end() const -> iterator {
12
- return iterator{util::slice(*this), context(), size()};
13
+ return iterator{util::slice(*this), context(), size(), maybe_sparse_};
13
14
  }
14
15
 
15
16
  auto value_for_array::size() const -> std::uint32_t {
@@ -20,12 +21,16 @@ auto value_for_array::size() const -> std::uint32_t {
20
21
  return length_ - 1;
21
22
  }
22
23
 
23
- value_for_array::iterator::iterator(v8::Local<v8::Array> array, v8::Local<v8::Context> context, std::uint32_t index) :
24
+ value_for_array::iterator::iterator(v8::Local<v8::Array> array, v8::Local<v8::Context> context, std::uint32_t index, bool maybe_sparse) :
24
25
  array_{array},
25
26
  context_{context},
26
- index_{index} {}
27
+ index_{index},
28
+ maybe_sparse_{maybe_sparse} {}
27
29
 
28
30
  auto value_for_array::iterator::operator*() const -> value_type {
31
+ if (maybe_sparse_ && !iv8::unmaybe(array_->Has(context_, index_))) {
32
+ throw js::type_error{u"Sparse arrays are not supported"};
33
+ }
29
34
  return iv8::unmaybe(array_->Get(context_, index_));
30
35
  }
31
36
 
package/value/array.h.cc CHANGED
@@ -12,14 +12,21 @@ class value_for_array : public handle_with_context<v8::Array> {
12
12
  class iterator;
13
13
  struct handle_data;
14
14
  using value_type = v8::Local<v8::Value>;
15
- using handle_with_context<v8::Array>::handle_with_context;
15
+
16
+ value_for_array() = default;
17
+ explicit value_for_array(v8::Local<v8::Context> context, v8::Local<v8::Array> array, bool maybe_sparse = true) :
18
+ handle_with_context{context, array},
19
+ maybe_sparse_{maybe_sparse} {}
20
+ explicit value_for_array(context_lock_witness lock, v8::Local<v8::Array> array, bool maybe_sparse = true) :
21
+ value_for_array{lock.context(), array, maybe_sparse} {}
16
22
 
17
23
  [[nodiscard]] auto begin() const -> iterator;
18
24
  [[nodiscard]] auto end() const -> iterator;
19
25
  [[nodiscard]] auto size() const -> std::uint32_t;
20
26
 
21
27
  private:
22
- mutable std::uint32_t length_{};
28
+ bool maybe_sparse_ : 1 = true;
29
+ mutable std::uint32_t length_ : 31 {};
23
30
  };
24
31
 
25
32
  class value_for_array::iterator : public util::random_access_iterator_facade<std::int32_t, std::int64_t> {
@@ -30,7 +37,7 @@ class value_for_array::iterator : public util::random_access_iterator_facade<std
30
37
  using value_type = value_for_array::value_type;
31
38
 
32
39
  iterator() = default;
33
- iterator(v8::Local<v8::Array> array, v8::Local<v8::Context> context, std::uint32_t index);
40
+ iterator(v8::Local<v8::Array> array, v8::Local<v8::Context> context, std::uint32_t index, bool maybe_sparse);
34
41
 
35
42
  auto operator*() const -> value_type;
36
43
  auto operator+=(difference_type offset) -> iterator&;
@@ -43,6 +50,7 @@ class value_for_array::iterator : public util::random_access_iterator_facade<std
43
50
  v8::Local<v8::Array> array_;
44
51
  v8::Local<v8::Context> context_;
45
52
  std::uint32_t index_{};
53
+ bool maybe_sparse_{};
46
54
  };
47
55
 
48
56
  static_assert(std::ranges::range<value_for_array>);
@@ -0,0 +1,136 @@
1
+ module;
2
+ #include <v8_js/version.h>
3
+ export module v8_js:external;
4
+ import :collected_handle;
5
+ import :lock;
6
+ import std;
7
+ import v8;
8
+
9
+ namespace js::iv8 {
10
+
11
+ // Untagged `v8::External` for pointer types.
12
+ export template <class Type>
13
+ class untagged_external : public v8::External {
14
+ public:
15
+ [[nodiscard]] auto value() const -> Type*;
16
+
17
+ static auto make(isolate_lock_witness lock, Type* value) -> v8::Local<untagged_external>;
18
+ static auto make_collected(const collected_handle_lock& lock, auto&&... args) -> v8::Local<untagged_external>
19
+ requires std::constructible_from<Type, decltype(args)...>;
20
+
21
+ private:
22
+ constexpr static auto type_tag = v8::ExternalPointerTypeTag{1};
23
+ };
24
+
25
+ // Tagged `v8::External` which holds a v8-managed host object.
26
+ export template <class Type>
27
+ class tagged_external : public v8::External {
28
+ private:
29
+ class holder;
30
+
31
+ public:
32
+ [[nodiscard]] auto get() const -> Type&;
33
+ [[nodiscard]] auto try_cast() const -> Type*;
34
+ static auto make_collected(const collected_handle_lock& lock, auto&&... args) -> v8::Local<tagged_external>
35
+ requires std::constructible_from<Type, decltype(args)...>;
36
+
37
+ private:
38
+ constexpr static auto type_tag = v8::ExternalPointerTypeTag{2};
39
+ };
40
+
41
+ // Inherited by `tagged_external<Type>::holder` instances
42
+ struct external_type_tag {
43
+ std::reference_wrapper<const std::type_info> type_tag;
44
+ };
45
+
46
+ // Host object with type tag
47
+ template <class Type>
48
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
49
+ class tagged_external<Type>::holder
50
+ : public external_type_tag,
51
+ public Type {
52
+ public:
53
+ explicit holder(auto&&... args)
54
+ requires std::constructible_from<Type, decltype(args)...> :
55
+ external_type_tag{typeid(Type)},
56
+ Type{std::forward<decltype(args)>(args)...} {}
57
+ };
58
+
59
+ // ---
60
+
61
+ // untagged_external
62
+ template <class Type>
63
+ auto untagged_external<Type>::value() const -> Type* {
64
+ #if V8_HAS_TAGGED_EXTERNAL
65
+ return static_cast<Type*>(Value(type_tag));
66
+ #else
67
+ return static_cast<Type*>(Value());
68
+ #endif
69
+ }
70
+
71
+ template <class Type>
72
+ auto untagged_external<Type>::make(isolate_lock_witness lock, Type* value) -> v8::Local<untagged_external> {
73
+ #if V8_HAS_TAGGED_EXTERNAL
74
+ return v8::External::New(lock.isolate(), value, type_tag).template As<untagged_external>();
75
+ #else
76
+ return v8::External::New(lock.isolate(), value).template As<untagged_external>();
77
+ #endif
78
+ }
79
+
80
+ template <class Type>
81
+ auto untagged_external<Type>::make_collected(const collected_handle_lock& lock, auto&&... args) -> v8::Local<untagged_external>
82
+ requires std::constructible_from<Type, decltype(args)...> {
83
+ using collected_handle_type = collected_handle<untagged_external, Type>;
84
+ auto collected_handle = collected_handle_type::make(lock, std::forward<decltype(args)>(args)...);
85
+ auto value =
86
+ #if V8_HAS_TAGGED_EXTERNAL
87
+ v8::External::New(lock.isolate(), collected_handle->get(), type_tag).template As<untagged_external>();
88
+ #else
89
+ v8::External::New(lock.isolate(), collected_handle->get()).template As<untagged_external>();
90
+ #endif
91
+ collected_handle_type::reset(lock, std::move(collected_handle), value);
92
+ return value;
93
+ }
94
+
95
+ // tagged_external
96
+ template <class Type>
97
+ auto tagged_external<Type>::get() const -> Type& {
98
+ auto* pointer = try_cast();
99
+ if (pointer == nullptr) {
100
+ throw std::logic_error{"external tag mismatch"};
101
+ } else {
102
+ return *pointer;
103
+ }
104
+ }
105
+
106
+ template <class Type>
107
+ auto tagged_external<Type>::try_cast() const -> Type* {
108
+ auto& object =
109
+ #if V8_HAS_TAGGED_EXTERNAL
110
+ *static_cast<holder*>(Value(type_tag));
111
+ #else
112
+ *static_cast<holder*>(Value());
113
+ #endif
114
+ if (object.type_tag.get() == typeid(Type)) {
115
+ return &object;
116
+ } else {
117
+ return nullptr;
118
+ }
119
+ }
120
+
121
+ template <class Type>
122
+ auto tagged_external<Type>::make_collected(const collected_handle_lock& lock, auto&&... args) -> v8::Local<tagged_external>
123
+ requires std::constructible_from<Type, decltype(args)...> {
124
+ using collected_handle_type = collected_handle<tagged_external, holder>;
125
+ auto collected_handle = collected_handle_type::make(lock, std::forward<decltype(args)>(args)...);
126
+ auto value =
127
+ #if V8_HAS_TAGGED_EXTERNAL
128
+ v8::External::New(lock.isolate(), collected_handle->get(), type_tag).template As<tagged_external>();
129
+ #else
130
+ v8::External::New(lock.isolate(), collected_handle->get()).template As<tagged_external>();
131
+ #endif
132
+ collected_handle_type::reset(lock, std::move(collected_handle), value);
133
+ return value;
134
+ }
135
+
136
+ } // namespace js::iv8
package/value/object.cc CHANGED
@@ -52,7 +52,7 @@ auto value_for_object::keys() const -> const value_for_array& {
52
52
  v8::IndexFilter::kIncludeIndices,
53
53
  v8::KeyConversionMode::kKeepNumbers
54
54
  );
55
- keys_ = value_for_array{context(), unmaybe(property_names)};
55
+ keys_ = value_for_array{context(), unmaybe(property_names), false};
56
56
  }
57
57
  return keys_;
58
58
  }
@@ -1,5 +1,3 @@
1
- module;
2
- #include <v8_js/version.h>
3
1
  module v8_js;
4
2
  import :primitive;
5
3
  import std;
@@ -68,14 +66,4 @@ value_for_date::operator js_clock::time_point() const {
68
66
  return js_clock::time_point{js_clock::duration{(*this)->ValueOf()}};
69
67
  }
70
68
 
71
- // `value_for_external`
72
- value_for_external::operator void*() const {
73
- #if V8_HAS_TAGGED_EXTERNAL
74
- // TODO: Use this feature
75
- return (*this)->Value(0);
76
- #else
77
- return (*this)->Value();
78
- #endif
79
- }
80
-
81
69
  } // namespace js::iv8
@@ -62,6 +62,7 @@ class value_for_external : public v8::Local<v8::External> {
62
62
  explicit value_for_external(null_lock_witness /*witness*/, v8::Local<v8::External> handle) :
63
63
  v8::Local<v8::External>{handle} {}
64
64
 
65
+ // nb: Unused at this time.
65
66
  [[nodiscard]] explicit operator void*() const;
66
67
  };
67
68
 
package/value/value.cc CHANGED
@@ -1,6 +1,7 @@
1
1
  export module v8_js:value;
2
2
  export import :array_buffer;
3
3
  export import :array;
4
+ export import :external;
4
5
  export import :fixed_array;
5
6
  export import :function;
6
7
  export import :object;