v8toolkit  0.0.1
Utility library for embedding V8 Javascript engine in a c++ program
javascript.cpp
Go to the documentation of this file.
1 #include <fstream>
2 #include <memory>
3 #include <v8-debug.h>
4 
5 #include "debugger.h"
6 
7 
8 namespace v8toolkit {
9 
10 boost::uuids::random_generator uuid_generator;
11 
12 std::atomic<int> script_id_counter(0);
13 
14 
15 Context::Context(std::shared_ptr<Isolate> isolate_helper,
16  v8::Local<v8::Context> context) :
17  isolate_helper(isolate_helper),
18  isolate(isolate_helper->get_isolate()),
19  context(v8::Global<v8::Context>(*isolate_helper, context))
20 {}
21 
22 
23 
25  return context.Get(isolate);
26 }
27 
28 
29 v8::Isolate * Context::get_isolate() const
30 {
31  return this->isolate;
32 }
33 
34 
35 std::shared_ptr<Isolate> Context::get_isolate_helper() const
36 {
37  return this->isolate_helper;
38 }
39 
41  return this->isolate_helper->json(json);
42 }
43 
44 
46 
47 
48 
50 // std::cerr << fmt::format("v8toolkit::Context being destroyed (isolate: {})", (void *)this->isolate) << std::endl;
51 }
52 
53 
54 std::shared_ptr<Script> Context::compile_from_file(const std::string & filename)
55 {
56  std::string contents;
57  time_t modification_time = 0;
58  if (!get_file_contents(filename, contents, modification_time)) {
59 
60  throw V8CompilationException(*this, std::string("Could not load file: ") + filename);
61  }
62 
63  return compile(contents, filename);
64 }
65 
66 
67 std::shared_ptr<Script> Context::compile(const std::string & javascript_source, const std::string & filename)
68 {
70 
71  // printf("Compiling %s\n", javascript_source.c_str());
72  // This catches any errors thrown during script compilation
73  v8::TryCatch try_catch(isolate);
74 
75  v8::Local<v8::String> source =
76  v8::String::NewFromUtf8(this->isolate, javascript_source.c_str());
77 
78  // this script origin data will be cached within the v8::UnboundScript associated with a Script object
79  // http://v8.paulfryzel.com/docs/master/classv8_1_1_script_compiler_1_1_source.html#ae71a5 fe18124d71f9acfcc872310d586
80  v8::ScriptOrigin script_origin(v8::String::NewFromUtf8(isolate, this->get_url(filename).c_str()),
81  v8::Integer::New(isolate, 0), // line offset
82  v8::Integer::New(isolate, 0), // column offset
83  v8::Local<v8::Boolean>(), // resource_is_shared_cross_origin
84  v8::Integer::New(isolate, ++Context::script_id_counter)
85  );
86 
87 
88  v8::MaybeLocal<v8::Script> compiled_script = v8::Script::Compile(context.Get(isolate), source, &script_origin);
89  if (compiled_script.IsEmpty()) {
90  throw V8CompilationException(isolate, v8::Global<v8::Value>(isolate, try_catch.Exception()));
91  }
92  return std::shared_ptr<Script>(new Script(shared_from_this(),
93  compiled_script.ToLocalChecked(), javascript_source));
94 
95 }
96 
97 
98 v8::Global<v8::Value> Context::run(const v8::Global<v8::Script> & script)
99 {
101 
102  // This catches any errors thrown during script compilation
103  v8::TryCatch try_catch(isolate);
104  // auto local_script = this->get_local(script);
105  auto local_script = v8::Local<v8::Script>::New(isolate, script);
106  auto maybe_result = local_script->Run(context.Get(isolate));
107  if (try_catch.HasCaught()) {
108 // printf("Context::run threw exception - about to print details:\n");
109  ReportException(isolate, &try_catch);
110  } else {
111 // printf("Context::run ran without throwing exception\n");
112  }
113 
114  if(maybe_result.IsEmpty()) {
115 
116  v8::Local<v8::Value> e = try_catch.Exception();
117  // print_v8_value_details(e);
118 
119  if(e->IsExternal()) {
120  auto anybase = (AnyBase *)v8::External::Cast(*e)->Value();
121  auto anyptr_exception_ptr = dynamic_cast<Any<std::exception_ptr> *>(anybase);
122  assert(anyptr_exception_ptr); // cannot handle other types at this time TODO: throw some other type of exception if this happens UnknownExceptionException or something
123 
124  // TODO: Are we leaking a copy of this exception by not cleaning up the exception_ptr ref count?
125  std::rethrow_exception(anyptr_exception_ptr->get());
126  } else {
127 // printf("v8 internal exception thrown: %s\n", *v8::String::Utf8Value(e));
128  throw V8Exception(isolate, v8::Global<v8::Value>(isolate, e));
129  }
130  }
131  v8::Local<v8::Value> result = maybe_result.ToLocalChecked();
132  return v8::Global<v8::Value>(isolate, result);
133 }
134 
135 
136 v8::Global<v8::Value> Context::run(const std::string & source)
137 {
138  return (*this)([this, source]{
139  auto compiled_code = compile(source);
140  return compiled_code->run();
141  });
142 }
143 
144 
145 
146 v8::Global<v8::Value> Context::run(const v8::Local<v8::Value> value)
147 {
148  return (*this)([this, value]{
149  return run(*v8::String::Utf8Value(value));
150  });
151 }
152 
153 
154 v8::Global<v8::Value> Context::run_from_file(const std::string & filename)
155 {
156  return compile_from_file(filename)->run();
157 }
158 
159 
160 v8::Global<v8::Context> const & Context::get_global_context() const {
161  return this->context;
162 }
163 
164 
165 std::future<std::pair<ScriptPtr, v8::Global<v8::Value>>>
166 Context::run_async(const std::string & source, std::launch launch_policy)
167 {
168  // copy code into the lambda so it isn't lost when this outer function completes
169  // right after creating the async
170  return (*this)([this, source, launch_policy]{
171  return this->compile(source)->run_async(launch_policy);
172  });
173 }
174 
175 
176 void Context::run_detached(const std::string & source)
177 {
178  (*this)([this, source]{
179  this->compile(source)->run_detached();
180  });
181 }
182 
183 
184 std::thread Context::run_thread(const std::string & source)
185 {
186  return (*this)([this, source]{
187  return this->compile(source)->run_thread();
188  });
189 }
190 
191 
192 boost::uuids::uuid const & Context::get_uuid() const {
193  return this->uuid;
194 }
196 
197  return boost::uuids::to_string(this->uuid);
198 }
199 
201  return fmt::format("v8toolkit://{}/{}", this->get_uuid_string(), name);
202 }
203 
204 
205 
206 
207 
208 v8::Local<v8::Value> Context::require(std::string const & filename, std::vector<std::string> const & paths) {
209  v8::Local<v8::Value> require_result;
210  v8toolkit::require(this->get_context(), filename,
211  require_result, paths, false, true, [this](RequireResult const & require_result) {},
212  [this](std::string const & filename){return this->get_url(filename);}
213  );
214  return require_result;
215 }
216 
217 void Context::require_directory(std::string const & directory_name) {
218 
219  foreach_file(directory_name, [&](std::string const & filename) {
220  this->require(filename, {directory_name});
221  });
222 
223 }
224 
225 Isolate::Isolate(v8::Isolate * isolate) : isolate(isolate)
226 {
227  isolate->SetData(0, (void *)&this->uuid);
228  v8toolkit::scoped_run(isolate, [this](v8::Isolate * isolate)->void {
229  this->global_object_template.Reset(isolate, v8::ObjectTemplate::New(this->get_isolate()));
230  });
231 }
232 
233 
235  auto unbound_script = this->script.Get(isolate)->GetUnboundScript();
236  assert(!unbound_script.IsEmpty());
237  return unbound_script;
238 }
239 
240 
241 Isolate::operator v8::Isolate*()
242 {
243  return this->isolate;
244 }
245 
246 
247 Isolate::operator v8::Local<v8::ObjectTemplate>()
248 {
249  return this->global_object_template.Get(this->isolate);
250 }
251 
252 
254 {
255  (*this)([this, callback](){
256  v8toolkit::add_print(isolate, get_object_template(), callback);
257  });
258  return *this;
259 }
260 
261 
263 {
264  (*this)([this](){
265  v8toolkit::add_print(isolate, get_object_template());
266  });
267  return *this;
268 }
269 
270 
271 void Isolate::add_require(std::vector<std::string> paths)
272 {
273  (*this)([this, paths]{
274  v8toolkit::add_require(isolate, get_object_template(), paths);
275  });
276 }
277 
278 
279 v8::Isolate * Isolate::get_isolate()
280 {
281  return this->isolate;
282 }
283 
284 
285 std::shared_ptr<Context> Isolate::create_context()
286 {
287  ISOLATE_SCOPED_RUN(this->isolate);
288  v8::TryCatch tc(this->isolate);
289 
290  auto ot = this->get_object_template();
291  auto context = v8::Context::New(this->isolate, NULL, ot);
292 
293 
294  if (tc.HasCaught() || context.IsEmpty()) {
295  throw V8ExecutionException(this->isolate, tc);
296  }
297 
298 
299  // can't use make_shared since the constructor is private
300  auto context_helper = new Context(shared_from_this(), context);
301 
302  return std::shared_ptr<Context>(context_helper);
303 }
304 
305 std::shared_ptr<DebugContext> Isolate::create_debug_context(short port) {
306  ISOLATE_SCOPED_RUN(this->isolate);
307  v8::TryCatch tc(this->isolate);
308 
309  auto ot = this->get_object_template();
310  auto context = v8::Context::New(this->isolate, NULL, ot);
311 
312 
313  if (tc.HasCaught() || context.IsEmpty()) {
314  throw V8ExecutionException(this->isolate, tc);
315  }
316 
317 
318  // can't use make_shared since the constructor is private
319  auto debug_context = new DebugContext(shared_from_this(), context, port);
320 
321  return std::shared_ptr<DebugContext>(debug_context);
322 
323 }
324 
325 
327 {
328  return global_object_template.Get(isolate);
329 }
330 
331 
333  v8::Local<v8::Context> debug_context = v8::Debug::GetDebugContext(this->isolate);
334  assert(!debug_context.IsEmpty());
335 
336  return v8toolkit::ContextPtr(new v8toolkit::Context(this->shared_from_this(), debug_context));
337 }
338 
340 {
341 #ifdef V8TOOLKIT_JAVASCRIPT_DEBUG
342  printf("Deleting isolate helper %p for isolate %p\n", this, this->isolate);
343 #endif
344 
345  wrapper_registery.cleanup_isolate(this->isolate);
346 
347  // must explicitly Reset this because the isolate will be
348  // explicitly disposed of before the Global is destroyed
349  this->global_object_template.Reset();
350  this->isolate->Dispose();
351 }
352 
354 {
355 
356  // evals an expression and tests for t
357  add_function("assert", [](const v8::FunctionCallbackInfo<v8::Value>& info) {
358  auto isolate = info.GetIsolate();
359  auto context = isolate->GetCurrentContext();
360 
361  v8::TryCatch tc(isolate);
362  auto script_maybe = v8::Script::Compile(context, info[0]->ToString());
363  if(tc.HasCaught()) {
364  // printf("Caught compilation error\n");
365  tc.ReThrow();
366  return;
367  }
368  auto script = script_maybe.ToLocalChecked();
369  auto result_maybe = script->Run(context);
370  if(tc.HasCaught()) {
371  // printf("Caught runtime exception\n");
372  tc.ReThrow();
373  return;
374  }
375  auto result = result_maybe.ToLocalChecked();
376 
377  bool default_value = false;
378  bool assert_result = result->BooleanValue(context).FromMaybe(default_value);
379  if (!assert_result) {
380  throw V8AssertionException(isolate, std::string("Expression returned false: ") + *v8::String::Utf8Value(info[0]));
381  }
382 
383  });
384 
385  // Does deep element inspection to determine equality
386  add_function("assert_contents", [this](const v8::FunctionCallbackInfo<v8::Value>& args){
387  auto isolate = args.GetIsolate();
388  if(args.Length() != 2 || !compare_contents(*this, args[0], args[1])) {
389  // printf("Throwing v8assertionexception\n");
390  throw V8AssertionException(*this, std::string("Data structures do not contain the same contents: ")+ stringify_value(isolate, args[0]).c_str() + " " + stringify_value(isolate, args[1]));
391  }
392  });
393 }
394 
395 
397  assert(!Platform::initialized);
398  expose_gc_value = true;
399 }
400 
401 
402 void Platform::set_max_memory(int new_memory_size_in_mb) {
403  Platform::memory_size_in_mb = new_memory_size_in_mb;
404 }
405 
406 
407 
408 void Platform::init(int argc, char ** argv, std::string const & snapshot_directory)
409 {
410  assert(!initialized);
411  process_v8_flags(argc, argv);
412 
413  if (expose_gc_value) {
415  }
416 
417 
418  // Initialize V8.
419  v8::V8::InitializeICU();
420 
421  // startup data is in the current directory
422 
423  if (snapshot_directory != "") {
424  v8::V8::InitializeExternalStartupData(snapshot_directory.c_str());
425  }
426 
427  Platform::platform = std::unique_ptr<v8::Platform>(v8::platform::CreateDefaultPlatform());
428  v8::V8::InitializePlatform(platform.get());
429  v8::V8::Initialize();
430 
431  initialized = true;
432 }
433 
435 {
436 
437  // Dispose the isolate and tear down V8.
438  v8::V8::Dispose();
439  v8::V8::ShutdownPlatform();
440 
441  platform.release();
442 };
443 
444 std::shared_ptr<Isolate> Platform::create_isolate()
445 {
446  assert(initialized);
447  v8::Isolate::CreateParams create_params;
448  if (Platform::memory_size_in_mb > 0) {
449  create_params.constraints.set_max_old_space_size(Platform::memory_size_in_mb);
450  }
451  create_params.array_buffer_allocator = (v8::ArrayBuffer::Allocator *) &Platform::allocator;
452 
453  // can't use make_shared since the constructor is private
454  auto isolate_helper = new Isolate(v8::Isolate::New(create_params));
455 
456  return std::shared_ptr<Isolate>(isolate_helper);
457 }
458 
459 
460 Script::Script(std::shared_ptr<Context> context_helper,
461  v8::Local<v8::Script> script,
462  std::string const & source_code) :
463  context_helper(context_helper),
464  isolate(context_helper->get_isolate()),
465  script(v8::Global<v8::Script>(isolate, script)),
466  script_source_code(source_code)
467 {
468 // std::cerr << "source code length: " << source_code.length() << std::endl;
469 }
470 
471 std::thread Script::run_thread()
472 {
473  // Holds on to a shared_ptr to the Script inside the thread object to make sure
474  // it isn't destroyed until the thread completes
475  // return type must be specified for Visual Studio 2015.2
476  // https://connect.microsoft.com/VisualStudio/feedback/details/1557383/nested-generic-lambdas-fails-to-compile-c
477  return std::thread([this](auto script_helper)->void{
478  (*this)([this]{
479  this->run();
480  });
481  }, shared_from_this());
482 }
483 
484 
486  run_thread().detach();
487 }
488 
490  return this->script_source_code;
491 }
492 
494 // return *v8::String::Utf8Value(this->script.Get(this->isolate)->GetUnboundScript()->GetSourceURL()->ToString());
495  return std::string("v8toolkit://") + this->context_helper->get_uuid_string() + "/" + *v8::String::Utf8Value(this->script.Get(this->isolate)->GetUnboundScript()->GetScriptName());
496 }
497 
498 
499 int64_t Script::get_script_id() const {
500  auto unbound_script = this->get_unbound_script();
501  auto id = unbound_script->GetId();
502  return id;
503 }
504 
505 
506 
507 
508 
509 bool Platform::initialized = false;
510 std::unique_ptr<v8::Platform> Platform::platform;
511 v8toolkit::ArrayBufferAllocator Platform::allocator;
512 
513 bool Platform::expose_gc_value = false;
514 int Platform::memory_size_in_mb = -1;
515 
516 } // end v8toolkit namespace
std::atomic< int > script_id_counter(0)
friend class Isolate
Definition: javascript.h:37
v8::Global< v8::Value > run_from_file(const std::string &filename)
Definition: javascript.cpp:154
std::string const & get_source_code() const
Definition: javascript.cpp:489
static void init(int argc, char **argv, std::string const &snapshot_directory="")
Definition: javascript.cpp:408
Isolate & add_print()
Definition: javascript.cpp:262
v8::Local< v8::UnboundScript > get_unbound_script() const
Definition: javascript.cpp:234
void expose_gc()
Definition: v8toolkit.cpp:52
v8::Global< v8::Value > run(const v8::Global< v8::Script > &script)
Definition: javascript.cpp:98
::std::string string
Definition: gtest-port.h:1097
void run_detached(const v8::Global< v8::Script > &script)
v8::Local< v8::Context > get_debug_context()
v8::Local< v8::Value > require(std::string const &filename, std::vector< std::string > const &paths)
Definition: javascript.cpp:208
std::shared_ptr< DebugContext > create_debug_context(short port)
Definition: javascript.cpp:305
std::string stringify_value(v8::Isolate *isolate, const v8::Local< v8::Value > &value, bool show_all_properties=false, std::vector< v8::Local< v8::Value >> &&processed_values=std::vector< v8::Local< v8::Value >>{})
Definition: v8helpers.cpp:187
boost::uuids::uuid const & get_uuid() const
Definition: javascript.cpp:192
void add_require(v8::Isolate *isolate, const v8::Local< v8::ObjectTemplate > &context, const std::vector< std::string > &paths)
Definition: v8toolkit.cpp:650
v8::Local< v8::ObjectTemplate > get_object_template()
Definition: javascript.cpp:326
ContextPtr get_debug_context()
Definition: javascript.cpp:332
std::thread run_thread()
Definition: javascript.cpp:471
boost::uuids::random_generator uuid_generator
Definition: javascript.cpp:10
v8::Isolate *const isolate
shortcut to the v8::isolate object instead of always going through the Isolate
Definition: javascript.h:69
v8::Isolate * get_isolate()
Definition: javascript.cpp:279
v8::Global< v8::Context > const & get_global_context() const
Definition: javascript.cpp:160
Definition: sample.cpp:29
void require_directory(std::string const &directory_name)
Definition: javascript.cpp:217
std::string get_source_location() const
Definition: javascript.cpp:493
std::shared_ptr< Script > compile(const std::string &source, const std::string &filename="Unspecified")
Definition: javascript.cpp:67
void add_require(std::vector< std::string > paths=std::vector< std::string >{"./"})
Definition: javascript.cpp:271
std::shared_ptr< Isolate > get_isolate_helper() const
Definition: javascript.cpp:35
nlohmann::json json
Definition: debugger.cpp:16
bool compare_contents(v8::Isolate *isolate, const v8::Local< v8::Value > &left, const v8::Local< v8::Value > &right)
Definition: v8toolkit.cpp:732
#define GLOBAL_CONTEXT_SCOPED_RUN(isolate, global_context)
Definition: v8toolkit.h:85
std::thread run_thread(const v8::Global< v8::Script > &script)
auto scoped_run(v8::Isolate *isolate, T callable) -> typename std::result_of< T()>::type
Definition: v8toolkit.h:111
static void cleanup()
Definition: javascript.cpp:434
static IsolatePtr create_isolate()
Definition: javascript.cpp:444
std::shared_ptr< Context > ContextPtr
Definition: javascript.h:367
bool require(v8::Local< v8::Context > context, std::string filename, v8::Local< v8::Value > &result, const std::vector< std::string > &paths, bool track_modification_times=false, bool use_cache=true, func::function< void(RequireResult const &)> callback=func::function< void(RequireResult const &)>(), func::function< std::string(std::string const &)> resource_name_callback=func::function< std::string(std::string const &)>())
Definition: v8toolkit.cpp:492
v8::Local< v8::Value > json(const std::string &json)
Definition: javascript.cpp:40
void cleanup_isolate(v8::Isolate *isolate)
void ReportException(v8::Isolate *isolate, v8::TryCatch *try_catch)
Definition: v8helpers.cpp:316
void add_function(std::string name, Function function)
Definition: javascript.h:299
v8::Local< v8::Context > get_context() const
Definition: javascript.cpp:24
v8::Isolate * get_isolate() const
Definition: javascript.cpp:29
void foreach_file(const std::string &directory_name, std::function< void(const std::string &)> const &callback)
Definition: v8toolkit.cpp:943
std::shared_ptr< Script > compile_from_file(const std::string &filename)
Definition: javascript.cpp:54
virtual ~Context()
Definition: javascript.cpp:49
int64_t get_script_id() const
Definition: javascript.cpp:499
std::future< std::pair< ScriptPtr, v8::Global< v8::Value > > > run_async(const std::string &source, std::launch launch_policy=std::launch::async|std::launch::deferred)
Definition: javascript.cpp:166
void process_v8_flags(int &argc, char **argv)
Definition: v8toolkit.cpp:46
#define ISOLATE_SCOPED_RUN(isolate)
Definition: v8toolkit.h:93
std::string get_url(std::string const &name) const
Definition: javascript.cpp:200
bool get_file_contents(std::string filename, std::string &file_contents, time_t &file_modification_time)
Definition: v8toolkit.cpp:249
void add_print(v8::Isolate *isolate, v8::Local< v8::ObjectTemplate > object_template, func::function< void(const std::string &)>=[](const std::string &s){printf("%s", s.c_str());})
Definition: v8toolkit.cpp:166
static void set_max_memory(int memory_size_in_mb)
Definition: javascript.cpp:402
std::string get_uuid_string() const
Definition: javascript.cpp:195
static void expose_gc()
Definition: javascript.cpp:396
V8ClassWrapperInstanceRegistry wrapper_registery
Definition: v8toolkit.cpp:42
std::shared_ptr< Context > create_context()
Definition: javascript.cpp:285