@auto_js/v8 0.0.8 → 0.0.10

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
@@ -64,6 +64,7 @@ if(v8_js STREQUAL isolated_v8_js)
64
64
  isolated/clock.cc
65
65
  isolated/cluster.cc
66
66
  isolated/executor.cc
67
+ isolated/memory_policy.cc
67
68
  isolated/platform.cc
68
69
  platform/delegate.cc
69
70
  platform/foreground_runner.cc
@@ -76,6 +77,7 @@ if(v8_js STREQUAL isolated_v8_js)
76
77
  isolated/clock.h.cc
77
78
  isolated/cluster.h.cc
78
79
  isolated/executor.h.cc
80
+ isolated/memory_policy.h.cc
79
81
  isolated/platform.h.cc
80
82
  platform/delegate.h.cc
81
83
  platform/foreground_runner.h.cc
package/_module.cc CHANGED
@@ -24,6 +24,8 @@ export import :evaluation.script;
24
24
  export import :isolated.agent;
25
25
  export import :isolated.clock;
26
26
  export import :isolated.cluster;
27
+ export import :isolated.executor;
28
+ export import :isolated.memory_policy;
27
29
  export import :isolated.platform;
28
30
  export import :platform.delegate;
29
31
  export import :platform.foreground_runner;
@@ -11,8 +11,8 @@ import v8;
11
11
 
12
12
  namespace js::iv8 {
13
13
 
14
- auto module_record::requests(context_lock_witness lock, v8::Local<v8::Module> module) -> std::vector<module_request> {
15
- auto requests_array = fixed_array{lock.context(), module->GetModuleRequests()};
14
+ auto module_record::requests(context_lock_witness lock) -> std::vector<module_request> {
15
+ auto requests_array = fixed_array{lock.context(), GetModuleRequests()};
16
16
  auto requests_view =
17
17
  requests_array |
18
18
  std::views::transform([ & ](v8::Local<v8::Data> value) -> module_request {
@@ -38,7 +38,7 @@ auto module_record::requests(context_lock_witness lock, v8::Local<v8::Module> mo
38
38
  return {std::from_range, std::move(requests_view)};
39
39
  }
40
40
 
41
- auto module_record::compile(context_lock_witness lock, v8::Local<v8::String> source_text, iv8::source_origin origin) -> expected_module_type {
41
+ auto module_record::compile(context_lock_witness lock, v8::Local<v8::String> source_text, iv8::source_origin origin) -> v8::MaybeLocal<module_record> {
42
42
  auto location = origin.location.value_or(source_location{});
43
43
  auto maybe_resource_name = js::transfer_in_strict<v8::MaybeLocal<v8::String>>(std::move(origin).name, lock);
44
44
  v8::Local<v8::String> resource_name{};
@@ -56,9 +56,7 @@ auto module_record::compile(context_lock_witness lock, v8::Local<v8::String> sou
56
56
  true,
57
57
  };
58
58
  v8::ScriptCompiler::Source source{source_text, script_origin};
59
- return unmaybe_one(lock, [ & ] -> v8::MaybeLocal<v8::Module> {
60
- return v8::ScriptCompiler::CompileModule(lock.isolate(), &source);
61
- });
59
+ return v8::ScriptCompiler::CompileModule(lock.isolate(), &source).As<module_record>();
62
60
  }
63
61
 
64
62
  auto module_record::create_synthetic(
@@ -66,12 +64,12 @@ auto module_record::create_synthetic(
66
64
  v8::Local<v8::String> module_name,
67
65
  std::span<const v8::Local<v8::String>> export_names,
68
66
  std::span<const v8::Local<v8::Data>> export_values
69
- ) -> v8::Local<v8::Module> {
67
+ ) -> v8::Local<module_record> {
70
68
  thread_local std::span<const v8::Local<v8::String>>* tl_export_names;
71
69
  thread_local std::span<const v8::Local<v8::Data>>* tl_export_values;
72
70
  tl_export_names = &export_names;
73
71
  tl_export_values = &export_values;
74
- auto scope_exit = util::scope_exit{[ & ]() {
72
+ auto scope_exit = util::scope_exit{[ & ] {
75
73
  tl_export_names = nullptr;
76
74
  tl_export_values = nullptr;
77
75
  }};
@@ -79,7 +77,7 @@ auto module_record::create_synthetic(
79
77
  // Make `v8::Module` record with immediate evaluation steps
80
78
  v8::Module::SyntheticModuleEvaluationSteps evaluation_steps =
81
79
  [](v8::Local<v8::Context> context, v8::Local<v8::Module> module) -> v8::MaybeLocal<v8::Value> {
82
- auto lock = [ & ]() -> context_lock_witness {
80
+ auto lock = [ & ] -> context_lock_witness {
83
81
  auto isolate_witness = isolate_lock_witness::make_witness(v8::Isolate::GetCurrent());
84
82
  return context_lock_witness::make_witness(isolate_witness, context);
85
83
  }();
@@ -102,19 +100,19 @@ auto module_record::create_synthetic(
102
100
  return resolver->GetPromise();
103
101
  };
104
102
  auto v8_export_names = v8::MemorySpan<const v8::Local<v8::String>>{export_names.begin(), export_names.end()};
105
- auto module_record = v8::Module::CreateSyntheticModule(lock.isolate(), module_name, v8_export_names, evaluation_steps);
103
+ auto synthetic_module = v8::Module::CreateSyntheticModule(lock.isolate(), module_name, v8_export_names, evaluation_steps);
106
104
 
107
105
  // "Link" the module, which is a no-op
108
106
  // auto null_callback = v8::Module::ResolveModuleByIndexCallback{nullptr};
109
107
  auto null_callback = v8::Module::ResolveModuleCallback{nullptr};
110
- unmaybe(module_record->InstantiateModule(lock.context(), null_callback));
108
+ unmaybe(synthetic_module->InstantiateModule(lock.context(), null_callback));
111
109
 
112
110
  // `Evaluate` invokes `evaluation_steps` above
113
- unmaybe(module_record->Evaluate(lock.context()));
114
- return module_record;
111
+ unmaybe(synthetic_module->Evaluate(lock.context()));
112
+ return synthetic_module.As<module_record>();
115
113
  }
116
114
 
117
- auto module_record::link(context_lock_witness lock, v8::Local<v8::Module> module, module_link_record link_record) -> void {
115
+ auto module_record::link(context_lock_witness lock, module_link_record link_record) -> void {
118
116
  // Initialize link state
119
117
  auto module_specifier_map = std::unordered_map<v8::Local<v8::Module>, unsigned, address_hash>{};
120
118
  auto module_id = 0U;
@@ -146,11 +144,13 @@ auto module_record::link(context_lock_witness lock, v8::Local<v8::Module> module
146
144
  return (*linker_ptr)(referrer, module_request_index);
147
145
  }
148
146
  };
149
- unmaybe(module->InstantiateModule(lock.context(), v8_callback));
147
+ // nb: It is strange that we need js here.
148
+ auto allow_js = v8::Isolate::AllowJavascriptExecutionScope{lock.isolate()};
149
+ unmaybe(InstantiateModule(lock.context(), v8_callback));
150
150
  linker_ptr = nullptr;
151
151
  }
152
152
 
153
- auto module_record::link(context_lock_witness lock, v8::Local<v8::Module> module) -> void {
153
+ auto module_record::link(context_lock_witness lock) -> void {
154
154
  // Probably a synthetic module
155
155
  auto v8_callback = v8::Module::ResolveModuleByIndexCallback{
156
156
  [](
@@ -161,12 +161,15 @@ auto module_record::link(context_lock_witness lock, v8::Local<v8::Module> module
161
161
  std::terminate();
162
162
  }
163
163
  };
164
- unmaybe(module->InstantiateModule(lock.context(), v8_callback));
164
+ unmaybe(InstantiateModule(lock.context(), v8_callback));
165
165
  }
166
166
 
167
- auto module_record::evaluate(context_lock_witness lock, v8::Local<v8::Module> module) -> expected_value_type {
168
- auto promise = unmaybe(module->Evaluate(lock.context())).As<v8::Promise>();
169
- if (module->IsGraphAsync()) {
167
+ auto module_record::evaluate(context_lock_witness lock) -> expected_value_type {
168
+ auto promise = [ & ] -> auto {
169
+ auto allow_js = v8::Isolate::AllowJavascriptExecutionScope{lock.isolate()};
170
+ return unmaybe(Evaluate(lock.context())).As<v8::Promise>();
171
+ }();
172
+ if (IsGraphAsync()) {
170
173
  throw std::runtime_error{"Module is async"};
171
174
  }
172
175
  if (promise->State() == v8::Promise::kRejected) {
@@ -45,53 +45,52 @@ export struct module_link_record {
45
45
  };
46
46
 
47
47
  // `v8::Module` utilities
48
- export class module_record {
48
+ export class module_record : public v8::Module {
49
49
  public:
50
- using expected_module_type = std::expected<v8::Local<v8::Module>, js::error_value>;
51
50
  using expected_value_type = std::expected<std::monostate, js::error_value>;
52
51
 
53
- static auto evaluate(context_lock_witness lock, v8::Local<v8::Module> module) -> expected_value_type;
54
- static auto link(context_lock_witness lock, v8::Local<v8::Module> module, module_link_record link_record) -> void;
55
- static auto link(context_lock_witness lock, v8::Local<v8::Module> module) -> void;
56
- static auto requests(context_lock_witness lock, v8::Local<v8::Module> module) -> std::vector<module_request>;
52
+ auto evaluate(context_lock_witness lock) -> expected_value_type;
53
+ auto link(context_lock_witness lock, module_link_record link_record) -> void;
54
+ auto link(context_lock_witness lock) -> void;
55
+ auto requests(context_lock_witness lock) -> std::vector<module_request>;
57
56
 
58
- static auto compile(context_lock_witness lock, auto source_text, iv8::source_origin origin) -> expected_module_type;
57
+ static auto compile(context_lock_witness lock, auto source_text, iv8::source_origin origin) -> v8::MaybeLocal<module_record>;
59
58
 
60
59
  template <class... Types>
61
- static auto create_synthetic(context_lock_witness lock, auto origin, std::tuple<std::in_place_t, Types...> module_interface) -> v8::Local<v8::Module>;
60
+ static auto create_synthetic(context_lock_witness lock, auto origin, std::tuple<std::in_place_t, Types...> module_interface) -> v8::Local<module_record>;
62
61
 
63
62
  template <class... Types>
64
- static auto create_synthetic(context_lock_witness lock, auto origin, std::tuple<Types...> module_interface) -> v8::Local<v8::Module>;
63
+ static auto create_synthetic(context_lock_witness lock, auto origin, std::tuple<Types...> module_interface) -> v8::Local<module_record>;
65
64
 
66
65
  template <class Type>
67
- static auto create_synthetic(context_lock_witness lock, auto origin, std::vector<Type> module_interface) -> v8::Local<v8::Module>;
66
+ static auto create_synthetic(context_lock_witness lock, auto origin, std::vector<Type> module_interface) -> v8::Local<module_record>;
68
67
 
69
68
  static auto create_synthetic(
70
69
  context_lock_witness lock,
71
70
  v8::Local<v8::String> module_name,
72
71
  std::span<const v8::Local<v8::String>> export_names,
73
72
  std::span<const v8::Local<v8::Data>> export_values
74
- ) -> v8::Local<v8::Module>;
73
+ ) -> v8::Local<module_record>;
75
74
 
76
75
  private:
77
- static auto compile(context_lock_witness lock, v8::Local<v8::String> source_text, iv8::source_origin origin) -> expected_module_type;
76
+ static auto compile(context_lock_witness lock, v8::Local<v8::String> source_text, iv8::source_origin origin) -> v8::MaybeLocal<module_record>;
78
77
  };
79
78
 
80
79
  // ---
81
80
 
82
- auto module_record::compile(context_lock_witness lock, auto source_text, iv8::source_origin origin) -> expected_module_type {
81
+ auto module_record::compile(context_lock_witness lock, auto source_text, iv8::source_origin origin) -> v8::MaybeLocal<module_record> {
83
82
  auto local_source_text = js::transfer_in_strict<v8::Local<v8::String>>(std::move(source_text), lock);
84
83
  return compile(lock, local_source_text, std::move(origin));
85
84
  }
86
85
 
87
86
  template <class... Types>
88
- auto module_record::create_synthetic(context_lock_witness lock, auto origin, std::tuple<std::in_place_t, Types...> module_interface) -> v8::Local<v8::Module> {
89
- auto [... ii ] = util::sequence<sizeof...(Types)>;
87
+ auto module_record::create_synthetic(context_lock_witness lock, auto origin, std::tuple<std::in_place_t, Types...> module_interface) -> v8::Local<module_record> {
88
+ constexpr auto [... ii ] = util::sequence<sizeof...(Types)>;
90
89
  return create_synthetic(lock, origin, std::tuple{std::get<ii + 1>(module_interface)...});
91
90
  }
92
91
 
93
92
  template <class... Types>
94
- auto module_record::create_synthetic(context_lock_witness lock, auto origin, std::tuple<Types...> module_interface) -> v8::Local<v8::Module> {
93
+ auto module_record::create_synthetic(context_lock_witness lock, auto origin, std::tuple<Types...> module_interface) -> v8::Local<module_record> {
95
94
  const auto& [... entries ] = module_interface;
96
95
  auto origin_local = js::transfer_in_strict<v8::Local<v8::String>>(std::move(origin), lock);
97
96
  auto names = js::transfer_in_strict<std::array<v8::Local<v8::String>, sizeof...(Types)>>(std::tuple{std::get<0>(entries)...}, lock);
@@ -100,7 +99,7 @@ auto module_record::create_synthetic(context_lock_witness lock, auto origin, std
100
99
  }
101
100
 
102
101
  template <class Type>
103
- auto module_record::create_synthetic(context_lock_witness lock, auto origin, std::vector<Type> module_interface) -> v8::Local<v8::Module> {
102
+ auto module_record::create_synthetic(context_lock_witness lock, auto origin, std::vector<Type> module_interface) -> v8::Local<module_record> {
104
103
  auto origin_local = js::transfer_in_strict<v8::Local<v8::String>>(std::move(origin), lock);
105
104
  auto name_locals = std::vector<v8::Local<v8::String>>{};
106
105
  name_locals.reserve(module_interface.size());
@@ -7,7 +7,7 @@ import v8;
7
7
 
8
8
  namespace js::iv8 {
9
9
 
10
- auto script::compile(context_lock_witness lock, v8::Local<v8::String> code_string, source_origin source_origin) -> expected_script_type {
10
+ auto script::compile(context_lock_witness lock, v8::Local<v8::String> code_string, source_origin source_origin) -> v8::MaybeLocal<script> {
11
11
  auto location = source_origin.location.value_or(source_location{});
12
12
  auto maybe_resource_name = js::transfer_in_strict<v8::MaybeLocal<v8::String>>(std::move(source_origin).name, lock);
13
13
  v8::Local<v8::String> resource_name{};
@@ -15,14 +15,14 @@ auto script::compile(context_lock_witness lock, v8::Local<v8::String> code_strin
15
15
  std::ignore = maybe_resource_name.ToLocal(&resource_name);
16
16
  v8::ScriptOrigin origin{resource_name, location.line, location.column};
17
17
  v8::ScriptCompiler::Source source{code_string, origin};
18
- return unmaybe_one(lock, [ & ] -> v8::MaybeLocal<v8::UnboundScript> {
19
- return v8::ScriptCompiler::CompileUnboundScript(lock.isolate(), &source);
20
- });
18
+ auto unbound_script = v8::ScriptCompiler::CompileUnboundScript(lock.isolate(), &source);
19
+ // nb: `As<script>()` doesn't work because `v8::UnboundScript` doesn't provide a `Cast` function
20
+ return std::bit_cast<v8::MaybeLocal<script>>(unbound_script);
21
21
  }
22
22
 
23
- auto script::run(context_lock_witness lock, v8::Local<v8::UnboundScript> script) -> v8::Local<v8::Value> {
24
- auto maybe_result = script->BindToCurrentContext()->Run(lock.context());
25
- return unmaybe(maybe_result);
23
+ auto script::run(context_lock_witness lock) -> v8::Local<v8::Value> {
24
+ auto allow_js = v8::Isolate::AllowJavascriptExecutionScope{lock.isolate()};
25
+ return unmaybe(BindToCurrentContext()->Run(lock.context()));
26
26
  }
27
27
 
28
28
  } // namespace js::iv8
@@ -7,23 +7,20 @@ import v8;
7
7
 
8
8
  namespace js::iv8 {
9
9
 
10
- export struct script {
10
+ export class script : public v8::UnboundScript {
11
11
  public:
12
- using expected_script_type = std::expected<v8::Local<v8::UnboundScript>, js::error_value>;
13
- using expected_value_type = std::optional<std::expected<js::value_t, js::error_value>>;
14
-
15
12
  // nb: It is undocumented (and even mentions "context independent"), but the script compiler
16
13
  // actually needs a context because it can throw an error and *that* would need a context.
17
- static auto compile(context_lock_witness lock, auto code_string, source_origin source_origin) -> expected_script_type;
18
- static auto run(context_lock_witness lock, v8::Local<v8::UnboundScript> script) -> v8::Local<v8::Value>;
14
+ static auto compile(context_lock_witness lock, auto code_string, source_origin source_origin) -> v8::MaybeLocal<script>;
15
+ auto run(context_lock_witness lock) -> v8::Local<v8::Value>;
19
16
 
20
17
  private:
21
- static auto compile(context_lock_witness lock, v8::Local<v8::String> code_string, source_origin source_origin) -> expected_script_type;
18
+ static auto compile(context_lock_witness lock, v8::Local<v8::String> code_string, source_origin source_origin) -> v8::MaybeLocal<script>;
22
19
  };
23
20
 
24
21
  // ---
25
22
 
26
- auto script::compile(context_lock_witness lock, auto code_string, source_origin source_origin) -> expected_script_type {
23
+ auto script::compile(context_lock_witness lock, auto code_string, source_origin source_origin) -> v8::MaybeLocal<script> {
27
24
  auto local_code_string = js::transfer_in_strict<v8::Local<v8::String>>(std::move(code_string), lock);
28
25
  return compile(lock, local_code_string, std::move(source_origin));
29
26
  }
@@ -9,9 +9,9 @@ namespace js::iv8 {
9
9
  template <class Tag>
10
10
  struct value_specialization;
11
11
 
12
- // nb: This corresponds to `js::napi::bound_value_of<T>`. `v8::Local<T>` is more like
13
- // `js::napi::value_of<T>`.
14
- template <class Tag>
12
+ // nb: This corresponds to `js::napi::value_of<T>`. `v8::Local<T>` is more like
13
+ // `js::napi::local_of<T>`.
14
+ export template <class Tag>
15
15
  class value_of : public value_specialization<Tag>::type {
16
16
  public:
17
17
  using value_specialization<Tag>::type::type;
@@ -50,9 +50,13 @@ struct value_specialization<date_tag> : std::type_identity<class value_for_date>
50
50
  template <>
51
51
  struct value_specialization<external_tag> : std::type_identity<class value_for_external> {};
52
52
 
53
+ // TODO: value_for_record
53
54
  template <>
54
55
  struct value_specialization<object_tag> : std::type_identity<class value_for_object> {};
55
56
 
57
+ template <>
58
+ struct value_specialization<list_tag> : std::type_identity<class value_for_object> {};
59
+
56
60
  template <>
57
61
  struct value_specialization<array_buffer_tag> : std::type_identity<class value_for_array_buffer> {};
58
62
 
package/isolated/agent.cc CHANGED
@@ -75,18 +75,22 @@ agent_host::agent_host(
75
75
  behavior_params params
76
76
  ) :
77
77
  storage_{std::move(storage)},
78
+ memory_policy_{std::move(params.memory_policy)},
78
79
  clock_{params.clock},
79
80
  destroy_callback_{destroy_callback},
80
81
  reset_handle_callback_{reset_handle_type{util::fn<&agent_host::remote_expiration_callback>, *this}},
81
82
  random_seed_{params.random_seed} {
82
83
  executor_.initialize([ & ](v8::Isolate* isolate) noexcept -> auto {
83
84
  isolate->SetData(0, this);
84
- auto create_params = v8::Isolate::CreateParams{};
85
- create_params.array_buffer_allocator_shared =
86
- std::shared_ptr<v8::ArrayBuffer::Allocator>(v8::ArrayBuffer::Allocator::NewDefaultAllocator());
87
- return create_params;
85
+ v8::Isolate::CreateParams params;
86
+ auto memory_params = memory_policy_->make_create_params();
87
+ params.constraints = memory_params.constraints;
88
+ params.array_buffer_allocator_shared = std::move(memory_params.allocator);
89
+ return params;
88
90
  });
89
- executor_.isolate()->SetCaptureStackTraceForUncaughtExceptions(true);
91
+ auto* isolate = executor_.isolate();
92
+ isolate->SetCaptureStackTraceForUncaughtExceptions(true);
93
+ memory_policy_->initialize(isolate);
90
94
  }
91
95
 
92
96
  agent_host::~agent_host() {
@@ -4,6 +4,7 @@ export module v8_js:isolated.agent;
4
4
  import :collected_handle;
5
5
  import :isolated.clock;
6
6
  import :isolated.executor;
7
+ import :isolated.memory_policy;
7
8
  import :platform.foreground_runner;
8
9
  import :remote;
9
10
  import std;
@@ -18,6 +19,7 @@ class agent_host_of;
18
19
 
19
20
  // `agent_host` behavior parameters
20
21
  export struct behavior_params {
22
+ isolated::memory_policy::covariant memory_policy;
21
23
  clock::any_clock clock;
22
24
  std::optional<double> random_seed;
23
25
  };
@@ -146,6 +148,7 @@ export class agent_host
146
148
  // Order matters for these members
147
149
  std::shared_ptr<agent_storage> storage_;
148
150
  isolated::executor executor_;
151
+ isolated::memory_policy::covariant memory_policy_;
149
152
 
150
153
  // Order doesn't really matter
151
154
  bool should_give_seed_{};
@@ -198,6 +201,9 @@ auto agent_handle_of<Type>::schedule(Task task, Args... args) const -> void {
198
201
  [](std::stop_token /*stop_token*/, const auto& host, const auto& task, auto&&... args) -> void {
199
202
  auto isolate_lock = isolate_execution_lock{host->executor().isolate()};
200
203
  std::visit([](auto& clock) -> void { clock.begin_tick(); }, host->clock());
204
+ // By default we disallow code execution except when explicitly annotated
205
+ auto disallow_js =
206
+ v8::Isolate::DisallowJavascriptExecutionScope{isolate_lock.isolate(), v8::Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE};
201
207
  task(lock{isolate_lock, static_cast<agent_host_of<Type>&>(*host)}, std::forward<decltype(args)>(args)...);
202
208
  },
203
209
  std::move(host),
@@ -42,7 +42,7 @@ auto cluster::make_agent(auto make_environment, behavior_params params, auto cal
42
42
  ) -> auto {
43
43
  auto host = std::make_shared<agent_host_of<environment_type>>(std::move(storage), params);
44
44
  auto isolate_lock = isolate_execution_lock{host->executor().isolate()};
45
- host->emplace_environment(util::elide{[ & ]() -> environment_type {
45
+ host->emplace_environment(util::elide{[ & ] -> environment_type {
46
46
  return make_environment(agent_lock{isolate_lock, *host});
47
47
  }});
48
48
  auto lock = agent_lock_of{isolate_lock, *host};
@@ -0,0 +1,49 @@
1
+ module v8_js;
2
+ import :isolated.memory_policy;
3
+
4
+ namespace js::iv8::isolated {
5
+
6
+ // memory_policy::none
7
+ auto memory_policy::none::make_create_params() -> create_params {
8
+ return {
9
+ .allocator = std::shared_ptr<v8::ArrayBuffer::Allocator>(v8::ArrayBuffer::Allocator::NewDefaultAllocator()),
10
+ .constraints = {}
11
+ };
12
+ }
13
+
14
+ // memory_policy::limited
15
+ auto memory_policy::limited::initialize(v8::Isolate* isolate) -> void {
16
+ isolate->AddNearHeapLimitCallback(
17
+ // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
18
+ [](void* data, std::size_t current_heap_limit, std::size_t initial_heap_limit) -> std::size_t {
19
+ auto& self = *static_cast<memory_policy::limited*>(data);
20
+ return self.near_heap_limit_callback(current_heap_limit, initial_heap_limit);
21
+ },
22
+ this
23
+ );
24
+ }
25
+
26
+ auto memory_policy::limited::make_create_params() -> create_params {
27
+ v8::ResourceConstraints constraints{};
28
+ constraints.ConfigureDefaultsFromHeapSize(0, memory_limit_);
29
+ return {
30
+ .allocator = std::shared_ptr<v8::ArrayBuffer::Allocator>(v8::ArrayBuffer::Allocator::NewDefaultAllocator()),
31
+ .constraints = constraints,
32
+ };
33
+ }
34
+
35
+ // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
36
+ auto memory_policy::limited::near_heap_limit_callback(std::size_t current_heap_limit, std::size_t /*initial_heap_limit*/) const -> std::size_t {
37
+ auto* isolate = v8::Isolate::GetCurrent();
38
+ v8::HeapStatistics heap;
39
+ isolate->GetHeapStatistics(&heap);
40
+
41
+ if (current_heap_limit > memory_limit_) {
42
+ auto& executor = agent_host::get_current(isolate).executor();
43
+ executor.terminate();
44
+ }
45
+ // Increase heap limit 80mb at a time. Experimentally derived.
46
+ return current_heap_limit + (80 << 20);
47
+ }
48
+
49
+ } // namespace js::iv8::isolated
@@ -0,0 +1,48 @@
1
+ export module v8_js:isolated.memory_policy;
2
+ import std;
3
+ import util;
4
+ import v8;
5
+
6
+ namespace js::iv8::isolated {
7
+
8
+ export class memory_policy {
9
+ protected:
10
+ ~memory_policy() = default;
11
+
12
+ public:
13
+ struct create_params;
14
+
15
+ // NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
16
+ class none;
17
+ // NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor)
18
+ class limited;
19
+
20
+ virtual auto initialize(v8::Isolate* isolate) -> void = 0;
21
+ virtual auto make_create_params() -> create_params = 0;
22
+ // virtual auto check_soft_limit(v8::Isolate* isolate) -> bool = 0;
23
+ using covariant = util::covariant_value<memory_policy, none, limited>;
24
+ };
25
+
26
+ struct memory_policy::create_params {
27
+ std::shared_ptr<v8::ArrayBuffer::Allocator> allocator;
28
+ v8::ResourceConstraints constraints;
29
+ };
30
+
31
+ class memory_policy::none final : public memory_policy {
32
+ public:
33
+ auto initialize(v8::Isolate* isolate) -> void override {};
34
+ auto make_create_params() -> create_params override;
35
+ };
36
+
37
+ class memory_policy::limited final : public memory_policy {
38
+ public:
39
+ explicit limited(std::size_t memory_limit_bytes) : memory_limit_{memory_limit_bytes} {}
40
+ auto initialize(v8::Isolate* isolate) -> void override;
41
+ auto make_create_params() -> create_params override;
42
+
43
+ private:
44
+ std::size_t memory_limit_;
45
+ [[nodiscard]] auto near_heap_limit_callback(std::size_t current_heap_limit, std::size_t initial_heap_limit) const -> std::size_t;
46
+ };
47
+
48
+ } // namespace js::iv8::isolated
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@auto_js/v8",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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/v8_exports": "^0.0.6",
9
- "@auto_js/js": "^0.0.8"
8
+ "@auto_js/js": "^0.0.10",
9
+ "@auto_js/v8_exports": "^0.0.8"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -15,7 +15,7 @@ struct platform_instance_handle {
15
15
  util::lockable<platform_instance_handle> shared_platform;
16
16
 
17
17
  platform_handle::platform_handle(const std::type_info& type, make_type* make) :
18
- platform_{util::elide{[ & ]() -> std::shared_ptr<v8::Platform> {
18
+ platform_{util::elide{[ & ] -> std::shared_ptr<v8::Platform> {
19
19
  auto lock = shared_platform.write();
20
20
  auto acquired_platform = lock->platform.lock();
21
21
  if (acquired_platform) {
@@ -83,7 +83,7 @@ export class instrumentation_delegate : virtual public v8::Platform {
83
83
 
84
84
  template <class Delegate>
85
85
  auto platform_handle::acquire() -> platform_handle {
86
- constexpr auto make = []() -> std::shared_ptr<v8::Platform> {
86
+ constexpr auto make = [] -> std::shared_ptr<v8::Platform> {
87
87
  return std::make_shared<Delegate>();
88
88
  };
89
89
  return platform_handle{typeid(Delegate), make};
@@ -35,13 +35,13 @@ auto foreground_runner::terminate() -> void {
35
35
  auto foreground_runner::get_for_priority(std::shared_ptr<foreground_runner> self, v8::TaskPriority priority) -> std::shared_ptr<v8::TaskRunner> {
36
36
  return util::template_switch(
37
37
  priority_numeric_from(priority),
38
- util::sequence<priority_count>,
38
+ util::sequence_cw<priority_count>,
39
39
  util::overloaded{
40
40
  [ & ](auto priority) -> std::shared_ptr<v8::TaskRunner> {
41
41
  using priority_runner_of = foreground_runner_of_priority<priority_enum_from(priority)>;
42
42
  return std::static_pointer_cast<priority_runner_of>(std::move(self));
43
43
  },
44
- []() -> std::shared_ptr<v8::TaskRunner> {
44
+ [] -> std::shared_ptr<v8::TaskRunner> {
45
45
  throw std::logic_error{"Invalid 'foreground_runner' priority"};
46
46
  },
47
47
  }
@@ -138,7 +138,7 @@ auto scheduler_foreground_thread::controller::run(
138
138
  });
139
139
  const auto any_stop_token = util::composite_stop_token{std::move(stop_token), termination_stop_token};
140
140
  while (!any_stop_token.stop_requested()) {
141
- auto [... timeout ] = [ & ]() -> auto {
141
+ auto [... timeout ] = [ & ] -> auto {
142
142
  if constexpr (requires { typename queue_type::clock_type; }) {
143
143
  return std::tuple{lock->queue.flush(queue_type::clock_type::now())};
144
144
  } else {
@@ -192,7 +192,7 @@ auto scheduler_background_threads::controller::run(
192
192
  });
193
193
  const auto any_stop_token = util::composite_stop_token{std::move(stop_token), termination_stop_token};
194
194
  while (!any_stop_token.stop_requested()) {
195
- auto [... timeout ] = [ & ]() -> auto {
195
+ auto [... timeout ] = [ & ] -> auto {
196
196
  if constexpr (requires { typename queue_type::clock_type; }) {
197
197
  return std::tuple{lock->queue.flush(queue_type::clock_type::now())};
198
198
  } else {
package/platform/task.cc CHANGED
@@ -43,7 +43,7 @@ class task_of final : public v8::Task {
43
43
 
44
44
  template <class... Args>
45
45
  auto make_task_of(std::invocable<std::stop_token, Args...> auto callback, Args&&... args) -> std::unique_ptr<v8::Task> {
46
- auto task = [ & ]() -> auto {
46
+ auto task = [ & ] -> auto {
47
47
  if constexpr (sizeof...(Args) == 0) {
48
48
  return std::move(callback);
49
49
  } else {
@@ -29,8 +29,8 @@ constexpr auto make_free_function_with_try_catch =
29
29
  auto bound_function = util::bind{
30
30
  [](callback_type& callback, Lock lock, const v8::FunctionCallbackInfo<v8::Value>& info) noexcept(Nx) -> void {
31
31
  // NOLINTNEXTLINE(cppcoreguidelines-slicing)
32
- auto result = invoke_internal_error_scope(lock, [ & ]() -> auto {
33
- auto run = util::regular_return{[ & ]() -> decltype(auto) {
32
+ auto result = invoke_internal_error_scope(lock, [ & ] -> auto {
33
+ auto run = util::regular_return{[ & ] -> decltype(auto) {
34
34
  return std::apply(
35
35
  callback,
36
36
  std::tuple_cat(
@@ -61,7 +61,7 @@ constexpr auto make_free_function_noexcept =
61
61
  using callback_type = decltype(callback);
62
62
  auto bound_function = util::bind{
63
63
  [](callback_type& callback, Lock lock, const v8::FunctionCallbackInfo<v8::Value>& /*info*/) noexcept -> void {
64
- auto run = util::regular_return{[ & ]() -> decltype(auto) {
64
+ auto run = util::regular_return{[ & ] -> decltype(auto) {
65
65
  return callback(lock);
66
66
  }};
67
67
  return js::transfer_in_strict<v8::Local<v8::Value>>(run().value_or(std::monostate{}), lock);
@@ -81,7 +81,7 @@ auto make_callback_storage(const auto& lock, auto function) {
81
81
  return std::tuple{callback, data};
82
82
  } else if constexpr (sizeof(bound_type) == sizeof(void*)) {
83
83
  // Trivial function of pointer type. Data() is the function data.
84
- auto data = [ & ]() -> auto {
84
+ auto data = [ & ] -> auto {
85
85
  #if V8_HAS_TAGGED_EXTERNAL
86
86
  return v8::External::New(lock.isolate(), std::bit_cast<void*>(bound), 0);
87
87
  #else
@@ -89,7 +89,7 @@ auto make_callback_storage(const auto& lock, auto function) {
89
89
  #endif
90
90
  }();
91
91
  const auto callback = v8::FunctionCallback{[](const v8::FunctionCallbackInfo<v8::Value>& info) -> void {
92
- auto invoke = [ & ]() -> auto {
92
+ auto invoke = [ & ] -> auto {
93
93
  #if V8_HAS_TAGGED_EXTERNAL
94
94
  // TODO: Use this feature
95
95
  return std::bit_cast<bound_type>(info.Data().As<v8::External>()->Value(0));
@@ -17,7 +17,7 @@ auto externalize_caught_error(context_lock_witness lock, v8::Local<v8::Value> va
17
17
  // Catch `iv8::pending_error` or `js::error`, converting to thrown v8 runtime error.
18
18
  [[nodiscard]] auto invoke_internal_error_scope(context_lock_witness lock, auto operation) {
19
19
  using result_type = std::invoke_result_t<decltype(operation)>;
20
- using value_type = type_t<[]() {
20
+ using value_type = type_t<[] {
21
21
  if constexpr (type<result_type> == type<void>) {
22
22
  return type<bool>;
23
23
  } else {
@@ -89,7 +89,7 @@ auto accept_v8_value::operator()(date_tag /*tag*/, visit_holder /*visit*/, js_cl
89
89
  auto accept_v8_value::operator()(error_tag /*tag*/, visit_holder /*visit*/, const js::error_value& subject) const
90
90
  -> js::referenceable_value<v8::Local<v8::Object>> {
91
91
  auto message = js::transfer_in<v8::Local<v8::String>>(subject.message(), witness());
92
- auto error = v8::Local<v8::Value>{[ & ]() -> v8::Local<v8::Value> {
92
+ auto error = v8::Local<v8::Value>{[ & ] -> v8::Local<v8::Value> {
93
93
  switch (subject.name()) {
94
94
  // These functions don't need an isolate somehow?
95
95
  default:
@@ -131,7 +131,7 @@ auto accept_v8_value::operator()(array_buffer_tag /*tag*/, visit_holder /*visit*
131
131
  auto accept_v8_value::operator()(shared_array_buffer_tag /*tag*/, visit_holder /*visit*/, js::shared_array_buffer&& subject) const
132
132
  -> js::referenceable_value<v8::Local<v8::SharedArrayBuffer>> {
133
133
  auto byte_length = subject.byte_length();
134
- auto backing_store = [ & ]() -> auto {
134
+ auto backing_store = [ & ] -> auto {
135
135
  if (byte_length == 0) {
136
136
  // v8 does not call the deleter if `byte_length` is zero. So the heap-allocated shared_ptr
137
137
  // trick does not work in that case.
@@ -1,5 +1,6 @@
1
1
  export module v8_js:accept;
2
2
  import :callback_storage;
3
+ import :handle.value;
3
4
  import :hash;
4
5
  import :value;
5
6
  import auto_js;
@@ -15,10 +16,10 @@ constexpr auto make_free_function(auto function);
15
16
 
16
17
  // Reference acceptor
17
18
  struct reaccept_v8_value {
18
- using reference_type = v8::Local<v8::Value>;
19
+ using reference_type = v8::Local<v8::Data>;
19
20
 
20
21
  template <class Type>
21
- constexpr auto operator()(std::type_identity<v8::Local<Type>> /*type*/, v8::Local<v8::Value> subject) const -> v8::Local<Type> {
22
+ constexpr auto operator()(std::type_identity<v8::Local<Type>> /*type*/, v8::Local<v8::Data> subject) const -> v8::Local<Type> {
22
23
  return subject.As<Type>();
23
24
  }
24
25
  };
@@ -62,6 +63,12 @@ struct accept_v8_primitive {
62
63
  return (*this)(number_tag_of<double>{}, visit, double{std::forward<decltype(subject)>(subject)});
63
64
  }
64
65
 
66
+ template <class Type>
67
+ auto operator()(number_tag_of<Type> tag, visit_holder visit, Type subject) const -> v8::Local<tag_to_v8<number_tag_of<Type>>> {
68
+ auto value = (*this)(tag, visit, std::int32_t{std::forward<decltype(subject)>(subject)});
69
+ return v8::Local<v8::Int32>{value}.As<tag_to_v8<number_tag_of<Type>>>();
70
+ }
71
+
65
72
  template <class Type>
66
73
  auto operator()(number_tag_of<Type> tag, visit_holder visit, auto&& subject) const -> v8::Local<tag_to_v8<number_tag_of<Type>>> {
67
74
  return (*this)(tag, visit, Type{std::forward<decltype(subject)>(subject)});
@@ -166,12 +173,10 @@ struct accept_v8_value : accept_v8_primitive {
166
173
  [](v8::Local<v8::Array> array, auto& self, auto& visit, auto /*&&*/ subject) -> void {
167
174
  auto&& range = util::into_range(std::forward<decltype(subject)>(subject));
168
175
  for (auto&& [ key, value ] : util::forward_range(std::forward<decltype(range)>(range))) {
169
- auto result = array->Set(
170
- self.context_,
171
- visit.first(std::forward<decltype(key)>(key), self),
172
- visit.second(std::forward<decltype(value)>(value), self)
173
- );
174
- unmaybe(result);
176
+ // nb: 'first' must run before 'second'
177
+ auto name = visit.first(std::forward<decltype(key)>(key), self);
178
+ auto item = visit.second(std::forward<decltype(value)>(value), self);
179
+ unmaybe(array->Set(self.context_, name, item));
175
180
  }
176
181
  },
177
182
  };
@@ -202,8 +207,8 @@ struct accept_v8_value : accept_v8_primitive {
202
207
  v8::Array::New(self.isolate(), Size),
203
208
  std::forward_as_tuple(self, visit, std::forward<decltype(subject)>(subject)),
204
209
  [](v8::Local<v8::Array> array, auto& self, auto& visit, auto /*&&*/ tuple) -> void {
205
- const auto [... indices ] = util::sequence<Size>;
206
- (..., [ & ]() -> void {
210
+ constexpr auto [... indices ] = util::sequence<Size>;
211
+ (..., [ & ] -> void {
207
212
  auto element = visit(indices, std::forward<decltype(tuple)>(tuple), self);
208
213
  unmaybe(array->Set(self.context_, indices, element));
209
214
  }());
@@ -220,12 +225,10 @@ struct accept_v8_value : accept_v8_primitive {
220
225
  [](v8::Local<v8::Object> object, auto& self, auto& visit, auto /*&&*/ subject) -> void {
221
226
  auto&& range = util::into_range(std::forward<decltype(subject)>(subject));
222
227
  for (auto&& [ key, value ] : util::forward_range(std::forward<decltype(range)>(range))) {
223
- auto result = object->Set(
224
- self.context_,
225
- visit.first(std::forward<decltype(key)>(key), self),
226
- visit.second(std::forward<decltype(value)>(value), self)
227
- );
228
- unmaybe(result);
228
+ // nb: 'first' must run before 'second'
229
+ auto name = visit.first(std::forward<decltype(key)>(key), self);
230
+ auto item = visit.second(std::forward<decltype(value)>(value), self);
231
+ unmaybe(object->Set(self.context_, name, item));
229
232
  }
230
233
  },
231
234
  };
@@ -280,8 +283,8 @@ struct accept_v8_value : accept_v8_primitive {
280
283
 
281
284
  template <std::size_t Size>
282
285
  auto accept_entry_pair_struct(this const auto& self, auto& visit, v8::Local<v8::Object> target, auto&& subject) -> void {
283
- const auto [... indices ] = util::sequence<Size>;
284
- (..., [ & ]() -> void {
286
+ constexpr auto [... indices ] = util::sequence<Size>;
287
+ (..., [ & ] -> void {
285
288
  auto accept_entry = accept_entry_pair<decltype(self), decltype(self)>{self};
286
289
  auto entry = visit(std::integral_constant<std::size_t, indices>{}, std::forward<decltype(subject)>(subject), accept_entry);
287
290
  unmaybe(target->Set(self.context_, entry.first, entry.second));
@@ -292,44 +295,56 @@ struct accept_v8_value : accept_v8_primitive {
292
295
  v8::Local<v8::Context> context_;
293
296
  };
294
297
 
295
- // `accept_v8_value` subclass, implementing specialized acceptor with lock type.
298
+ // Lock delegate for `accept_v8_value_with`. In the `context_lock_witness` case, `accept_v8_value`
299
+ // holds that lock by type. If the lock is more specialized we need to hold another reference.
296
300
  template <class Lock>
297
- struct accept_v8_value_with;
301
+ struct accept_v8_value_lock_delegate;
298
302
 
299
303
  template <>
300
- struct accept_v8_value_with<context_lock_witness> : accept_v8_value {
304
+ struct accept_v8_value_lock_delegate<context_lock_witness> : accept_v8_value {
301
305
  using accept_v8_value::accept_v8_value;
302
- using accept_v8_value::operator();
303
- using accept_v8_value::witness;
304
-
305
- // function instantiation
306
- auto operator()(function_prototype_tag /*tag*/, visit_holder /*visit*/, auto subject) const -> v8::Local<iv8::Function> {
307
- auto [ function, length ] = make_free_function<context_lock_witness>(std::move(subject).callback);
308
- auto [ callback, data ] = make_callback_storage(witness(), std::move(function));
309
- return unmaybe(v8::Function::New(witness().context(), callback, data, length, v8::ConstructorBehavior::kThrow).template As<iv8::Function>());
310
- }
311
306
  };
312
307
 
313
308
  template <class Lock>
314
- struct accept_v8_value_with : accept_v8_value {
309
+ struct accept_v8_value_lock_delegate : accept_v8_value {
315
310
  public:
316
- accept_v8_value_with(auto* transfer, const Lock& lock) :
311
+ accept_v8_value_lock_delegate(auto* transfer, const Lock& lock) :
317
312
  accept_v8_value{transfer, lock},
318
313
  lock_{lock} {}
319
314
 
320
- using accept_v8_value::operator();
321
-
322
- // function instantiation
323
- auto operator()(function_prototype_tag /*tag*/, visit_holder /*visit*/, auto subject) const -> v8::Local<iv8::Function> {
324
- auto [ function, length ] = make_free_function<Lock>(std::move(subject).callback);
325
- auto [ callback, data ] = make_callback_storage(lock_.get(), std::move(function));
326
- return v8::Function::New(lock_.get().context(), callback, data, length, v8::ConstructorBehavior::kThrow);
327
- }
315
+ [[nodiscard]] auto witness() const -> const Lock& { return lock_; }
328
316
 
329
317
  private:
330
318
  std::reference_wrapper<const Lock> lock_;
331
319
  };
332
320
 
321
+ // `accept_v8_value` subclass, implementing specialized acceptor with lock type.
322
+ template <class Lock>
323
+ struct accept_v8_value_with : accept_v8_value_lock_delegate<Lock> {
324
+ using accept_v8_value_lock_delegate<Lock>::accept_v8_value_lock_delegate;
325
+ using accept_v8_value_lock_delegate<Lock>::operator();
326
+ using accept_v8_value_lock_delegate<Lock>::witness;
327
+
328
+ // object template instantiation
329
+ auto operator()(object_prototype_tag /*tag*/, visit_holder /*visit*/, v8::Local<v8::ObjectTemplate> subject) const -> v8::Local<v8::Object> {
330
+ return unmaybe(subject->NewInstance(witness().context()));
331
+ }
332
+
333
+ // function instantiation (from template)
334
+ auto operator()(function_prototype_tag /*tag*/, visit_holder /*visit*/, v8::Local<v8::FunctionTemplate> subject) const -> v8::Local<iv8::Function> {
335
+ auto fn = unmaybe(subject->GetFunction(witness().context()));
336
+ return v8::Local<v8::Function>{fn}.As<iv8::Function>();
337
+ }
338
+
339
+ // function instantiation (from native type)
340
+ auto operator()(function_prototype_tag /*tag*/, visit_holder /*visit*/, auto subject) const -> v8::Local<iv8::Function> {
341
+ auto [ function, length ] = make_free_function<context_lock_witness>(std::move(subject).callback);
342
+ auto [ callback, data ] = make_callback_storage(witness(), std::move(function));
343
+ auto fn = unmaybe(v8::Function::New(witness().context(), callback, data, length, v8::ConstructorBehavior::kThrow));
344
+ return v8::Local<v8::Function>{fn}.As<iv8::Function>();
345
+ }
346
+ };
347
+
333
348
  // Acceptor with template environment
334
349
  template <class Meta, class Lock>
335
350
  struct accept_v8_template : accept_v8_primitive {
@@ -338,6 +353,36 @@ struct accept_v8_template : accept_v8_primitive {
338
353
  accept_v8_primitive{transfer, lock},
339
354
  lock_{lock} {}
340
355
 
356
+ // cast primitive to prototype
357
+ template <std::convertible_to<primitive_tag> Tag>
358
+ auto operator()(Tag tag, auto& visit, auto&& subject) const -> v8::Local<v8::Template>
359
+ // refuse on bigint, which requires a context for some reason.
360
+ requires(!std::convertible_to<Tag, bigint_tag>) {
361
+ const accept_v8_primitive& accept = *this;
362
+ auto result = accept(tag, visit, std::forward<decltype(subject)>(subject));
363
+ auto value = v8::Local<v8::Value>{dispatch_referenceable(result)};
364
+ // nb: We are smuggling a `v8::Primitive` as a `v8::Template`
365
+ return std::bit_cast<v8::Local<v8::Template>>(value);
366
+ }
367
+
368
+ // object
369
+ auto operator()(this const auto& self, dictionary_tag /*tag*/, auto& visit, auto&& subject)
370
+ -> js::deferred_receiver<v8::Local<v8::ObjectTemplate>, decltype(self), decltype(visit), decltype(subject)> {
371
+ return {
372
+ v8::ObjectTemplate::New(self.isolate()),
373
+ std::forward_as_tuple(self, visit, std::forward<decltype(subject)>(subject)),
374
+ [](v8::Local<v8::ObjectTemplate> object, auto& self, auto& visit, auto /*&&*/ subject) -> void {
375
+ auto&& range = util::into_range(std::forward<decltype(subject)>(subject));
376
+ for (auto&& [ key, value ] : util::forward_range(std::forward<decltype(range)>(range))) {
377
+ // nb: 'first' must run before 'second'
378
+ auto name = v8::Local<v8::Template>{visit.first(std::forward<decltype(key)>(key), self)};
379
+ auto item = visit.second(std::forward<decltype(value)>(value), self);
380
+ object->Set(name.As<v8::Name>(), item);
381
+ }
382
+ },
383
+ };
384
+ }
385
+
341
386
  // function template
342
387
  auto operator()(function_prototype_tag /*tag*/, visit_holder /*visit*/, auto subject) const -> v8::Local<v8::FunctionTemplate> {
343
388
  auto [ function, length ] = make_free_function<Lock>(std::move(subject).callback);
@@ -370,6 +415,20 @@ struct accept_v8_property_value {
370
415
  accept_value<Meta, Type> second;
371
416
  };
372
417
 
418
+ // Forward `value_of<T>`
419
+ template <class Tag>
420
+ struct accept_v8_value_of {
421
+ public:
422
+ explicit accept_v8_value_of(auto* /*transfer*/) {}
423
+
424
+ auto operator()(Tag /*tag*/, visit_holder /*visit*/, value_of<Tag> subject) const -> value_of<Tag> {
425
+ return subject;
426
+ }
427
+
428
+ consteval static auto accept_tags_of() { return std::tuple{Tag{}}; }
429
+ consteval static auto types(auto /*recursive*/) { return util::type_pack{}; }
430
+ };
431
+
373
432
  // `return_into(...)`
374
433
  struct return_into_marker {};
375
434
 
@@ -475,6 +534,12 @@ struct accept<Meta, v8::Local<Type>> : iv8::accept_v8_value_with<typename Meta::
475
534
  using iv8::accept_v8_value_with<typename Meta::accept_context_type>::accept_v8_value_with;
476
535
  };
477
536
 
537
+ // forward `value_of<T>`
538
+ template <class Meta, class Tag>
539
+ struct accept<Meta, iv8::value_of<Tag>> : iv8::accept_v8_value_of<Tag> {
540
+ using iv8::accept_v8_value_of<Tag>::accept_v8_value_of;
541
+ };
542
+
478
543
  // templates
479
544
  template <class Meta, class Type>
480
545
  requires std::is_convertible_v<Type, v8::Template>
package/transfer/visit.cc CHANGED
@@ -25,7 +25,7 @@ struct visit_property_name {
25
25
 
26
26
  template <class Accept>
27
27
  auto operator()(v8::Local<v8::Primitive> subject, const Accept& accept) -> accept_target_t<Accept> {
28
- return visit_.get().lookup_or_visit(subject, [ & ]() -> accept_target_t<Accept> {
28
+ return visit_.get().lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
29
29
  auto accept_as = [ & ]<class Tag>(Tag tag) -> auto {
30
30
  auto value = value_of{witness(), subject.As<tag_to_v8<Tag>>()};
31
31
  return accept(tag, *this, value);
@@ -80,7 +80,7 @@ struct visit_cached_immediate : Visit {
80
80
  auto operator()(auto subject, const Accept& accept) -> accept_target_t<Accept>
81
81
  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124954
82
82
  requires requires { this->immediate(subject, accept); } {
83
- return lookup_or_visit(subject, [ & ]() -> accept_target_t<Accept> {
83
+ return lookup_or_visit(subject, [ & ] -> accept_target_t<Accept> {
84
84
  return immediate(subject, accept);
85
85
  });
86
86
  }
@@ -276,7 +276,7 @@ struct visit_value : visit_flat_value<Target> {
276
276
  auto operator()(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 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{
@@ -313,7 +313,7 @@ struct visit_value : visit_flat_value<Target> {
313
313
  [](auto /*tag*/, auto next) -> accept_target_t<Accept> { return next(); },
314
314
 
315
315
  // Slow path
316
- [ & ]() -> accept_target_t<Accept> {
316
+ [ & ] -> accept_target_t<Accept> {
317
317
  if (subject->IsObject()) {
318
318
  return immediate(subject.As<v8::Object>(), accept);
319
319
  } else {
@@ -327,7 +327,7 @@ struct visit_value : visit_flat_value<Target> {
327
327
 
328
328
  template <class Accept>
329
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> {
330
+ return 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{
@@ -337,7 +337,7 @@ struct visit_value : visit_flat_value<Target> {
337
337
  [ & ](shared_array_buffer_tag /*tag*/, auto next) -> accept_target_t<Accept> { return next(); },
338
338
 
339
339
  // Slow path
340
- [ & ]() -> accept_target_t<Accept> {
340
+ [ & ] -> accept_target_t<Accept> {
341
341
  return immediate(subject, accept);
342
342
  }
343
343
  }
@@ -380,7 +380,7 @@ struct visit_value : visit_flat_value<Target> {
380
380
  template <class Accept>
381
381
  auto immediate(v8::Local<v8::Array> subject, const Accept& accept) -> accept_target_t<Accept> {
382
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::Object>()});
383
+ return accept(list_tag{}, visit_entry, value_of{witness(), subject.As<v8::Array>()});
384
384
  }
385
385
 
386
386
  // function template
@@ -433,10 +433,55 @@ struct visit_value : visit_flat_value<Target> {
433
433
  v8::Local<v8::Context> context_;
434
434
  };
435
435
 
436
+ // Forward `value_of<T>` back to acceptor
437
+ template <class Tag>
438
+ struct visit_v8_value_of {
439
+ private:
440
+ using visit_type = visit_value<void>;
441
+
442
+ public:
443
+ constexpr explicit visit_v8_value_of(auto* transfer, context_lock_witness lock) : visit_{transfer, lock} {}
444
+
445
+ template <class Accept>
446
+ constexpr auto operator()(value_of<Tag> subject, const Accept& accept) -> accept_target_t<Accept> {
447
+ return accept(Tag{}, visit_, subject);
448
+ }
449
+
450
+ template <class Accept>
451
+ requires std::is_same_v<Tag, object_tag>
452
+ constexpr auto operator()(value_of<object_tag> subject, const Accept& accept) -> accept_target_t<Accept> {
453
+ auto visit_entry = visit_entry_pair<visit_property_name<visit_type>, visit_type&>{visit_};
454
+ return accept(object_tag{}, visit_entry, subject);
455
+ }
456
+
457
+ template <class Accept>
458
+ requires std::is_same_v<Tag, list_tag>
459
+ constexpr auto operator()(value_of<list_tag> subject, const Accept& accept) -> accept_target_t<Accept> {
460
+ auto visit_entry = visit_entry_pair<visit_property_name<visit_type>, visit_type&>{visit_};
461
+ return accept(list_tag{}, visit_entry, subject);
462
+ }
463
+
464
+ consteval static auto types(auto /*recursive*/) { return util::type_pack{}; }
465
+
466
+ private:
467
+ visit_type visit_;
468
+ };
469
+
436
470
  // Template visitor needs no lock. The acceptor will instantiate values.
437
471
  struct visit_template {
438
472
  explicit visit_template(auto* /*transfer*/) {}
439
473
 
474
+ template <class Accept>
475
+ auto operator()(v8::Local<v8::Template> subject, const Accept& accept) const -> accept_target_t<Accept> {
476
+ if (subject->IsFunctionTemplate()) {
477
+ return (*this)(subject.As<v8::FunctionTemplate>(), accept);
478
+ } else if (subject->IsObjectTemplate()) {
479
+ return (*this)(subject.As<v8::ObjectTemplate>(), accept);
480
+ } else {
481
+ return accept_target_t<Accept>{subject};
482
+ }
483
+ }
484
+
440
485
  template <class Accept>
441
486
  auto operator()(v8::Local<v8::FunctionTemplate> subject, const Accept& accept) const -> accept_target_t<Accept> {
442
487
  return accept(function_prototype_tag{}, *this, subject);
@@ -467,6 +512,15 @@ struct visit<Meta, v8::Local<Type>> : iv8::visit_value_with<Meta> {
467
512
  using iv8::visit_value_with<Meta>::visit_value_with;
468
513
  };
469
514
 
515
+ // value_of<T> visitor
516
+ template <class Meta, class Tag>
517
+ struct visit<Meta, iv8::value_of<Tag>> : iv8::visit_v8_value_of<Tag> {
518
+ using iv8::visit_v8_value_of<Tag>::visit_v8_value_of;
519
+ };
520
+
521
+ template <class Tag>
522
+ struct visit_subject_for<iv8::value_of<Tag>> : std::type_identity<v8::Local<v8::Value>> {};
523
+
470
524
  // Template visitor
471
525
  template <class Meta, class Type>
472
526
  requires std::is_convertible_v<Type, v8::Template>
package/value/function.cc CHANGED
@@ -10,11 +10,12 @@ namespace js::iv8 {
10
10
 
11
11
  template <class Result>
12
12
  auto Function::apply(context_lock_witness lock, auto&& args) -> Result {
13
- auto result = [ & ]() {
13
+ auto result = [ & ] {
14
14
  using args_type = std::vector<v8::Local<v8::Value>>;
15
15
  auto handle_scope = v8::EscapableHandleScope{lock.isolate()};
16
16
  auto undefined = js::transfer_in_strict<v8::Local<v8::Value>>(std::monostate{}, lock);
17
17
  auto argv = js::transfer_in_strict<args_type>(std::forward<decltype(args)>(args), lock);
18
+ auto allow_js = v8::Isolate::AllowJavascriptExecutionScope{lock.isolate()};
18
19
  auto result = unmaybe(v8::Function::Call(lock.context(), undefined, argv.size(), argv.data()));
19
20
  return handle_scope.Escape(result);
20
21
  }();
@@ -23,7 +24,7 @@ auto Function::apply(context_lock_witness lock, auto&& args) -> Result {
23
24
 
24
25
  template <class Result>
25
26
  auto Function::call(context_lock_witness lock, auto&&... args) -> Result {
26
- auto result = [ & ]() {
27
+ auto result = [ & ] {
27
28
  using args_type = std::array<v8::Local<v8::Value>, sizeof...(args) + 1>;
28
29
  auto handle_scope = v8::EscapableHandleScope{lock.isolate()};
29
30
  auto argv = js::transfer_in_strict<args_type>(
@@ -33,6 +34,7 @@ auto Function::call(context_lock_witness lock, auto&&... args) -> Result {
33
34
  ),
34
35
  lock
35
36
  );
37
+ auto allow_js = v8::Isolate::AllowJavascriptExecutionScope{lock.isolate()};
36
38
  return handle_scope.Escape(unmaybe(v8::Function::Call(lock.context(), argv[ 0 ], argv.size() - 1, argv.data() + 1)));
37
39
  }();
38
40
  return js::transfer_out<Result>(result, lock);