v8toolkit  0.0.1
Utility library for embedding V8 Javascript engine in a c++ program
v8toolkit.cpp
Go to the documentation of this file.
1 
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <assert.h>
6 
7 #include <vector>
8 #include <iostream>
9 #include <utility>
10 #include <string>
11 #include <sstream>
12 #include <regex>
13 #include <mutex>
14 #include <set>
15 #include <map>
16 #include <fmt/format.h>
17 #include <boost/format.hpp>
18 
19 #include "v8_class_wrapper.h"
20 #include "./v8toolkit.h"
21 
22 
23 
24 
25 
26 void* operator new[](size_t size, const char* pName, int flags, unsigned debugFlags, const char* file, int line)
27 {
28  return malloc(size);
29 }
30 
31 void* operator new[](size_t size, size_t alignment, size_t alignmentOffset, const char* pName, int flags, unsigned debugFlags, const char* file, int line)
32 {
33  return malloc(size);
34 }
35 
36 namespace v8toolkit {
37 
38 // used in v8_class_wrapper_impl.h to track which global names have been used
39 std::map<v8::Isolate *, std::vector<std::string>> used_constructor_name_list_map;
40 
41 // used in v8_class_wrapper.h to store callbacks for cleaning up wrapper objects when an isolate is destroyed
43 
44 using namespace ::v8toolkit::literals;
45 
46 void process_v8_flags(int & argc, char ** argv)
47 {
48  v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
49 }
50 
51 
52 void expose_gc()
53 {
54  static const char * EXPOSE_GC = "--expose-gc";
55  v8::V8::SetFlagsFromString(EXPOSE_GC, strlen(EXPOSE_GC));
56 }
57 
58 
60  message(message + get_stack_trace_string(v8::StackTrace::CurrentStackTrace(v8::Isolate::GetCurrent(), 100)))
61 {
62 }
63 
64 
65 void add_variable(v8::Isolate * isolate, const v8::Local<v8::ObjectTemplate> & object_template, const char * name, const v8::Local<v8::Data> template_to_attach)
66 {
67  object_template->Set(isolate, name, template_to_attach);
68 }
69 
70 
71 void add_variable(const v8::Local<v8::Context> context, const v8::Local<v8::Object> & object, const char * name, const v8::Local<v8::Value> value)
72 {
73  auto isolate = context->GetIsolate();
74  (void)object->Set(context, v8::String::NewFromUtf8(isolate, name), value);
75 }
76 
77 
78 void add_function(v8::Isolate * isolate, const v8::Local<v8::ObjectTemplate> & object_template, const char * name, void(*function)(const v8::FunctionCallbackInfo<v8::Value>&)) {
79  object_template->Set(isolate, name, make_function_template(isolate, function, name));
80 }
81 
82 
83 
84 std::string _format_helper(const v8::FunctionCallbackInfo<v8::Value>& args, bool append_newline)
85 {
86  std::stringstream sstream;
87  auto values = get_all_values(args);
88 
89  if (args.Length() > 0) {
90  auto format = boost::format(*v8::String::Utf8Value(values[0]));
91 
92  unsigned int i;
93  for (i = 1; format.remaining_args() > 0; i++) {
94  if (i < values.size()) {
95  format % *v8::String::Utf8Value(values[i]);
96  } else {
97  format % "";
98  }
99  }
100  sstream << format;
101  while (i < values.size()) {
102  sstream << " " << *v8::String::Utf8Value(values[i]);
103  i++;
104  }
105  }
106  if (append_newline) {
107  sstream << std::endl;
108  }
109  return sstream.str();
110 }
111 
112 
113 
114 // takes a format string and some javascript objects and does a printf-style print using boost::format
115 // fills missing parameters with empty strings and prints any extra parameters with spaces between them
116 std::string _printf_helper(const v8::FunctionCallbackInfo<v8::Value>& args, bool append_newline) {
117  return _format_helper(args, append_newline);
118 }
119 
120 
121 // Returns the values in a FunctionCallbackInfo object breaking out first-level arrays into their
122 // contained values (but not subsequent arrays)
123 std::vector<v8::Local<v8::Value>> get_all_values(const v8::FunctionCallbackInfo<v8::Value>& args, int depth) {
124  std::vector<v8::Local<v8::Value>> values;
125 
126  auto isolate = args.GetIsolate();
127  auto context = isolate->GetCurrentContext();
128 
129  for (int i = 0; i < args.Length(); i++) {
130  if (args[i]->IsArray()) {
131  auto array = v8::Object::Cast(*args[i]);
132  int i = 0;
133  while(array->Has(context, i).FromMaybe(false)) {
134  values.push_back(array->Get(context, i).ToLocalChecked());
135  i++;
136  }
137  } else {
138  values.push_back(args[i]);
139  }
140  }
141  return values;
142 }
143 
144 
145 
146 // prints out arguments with a space between them
147 std::string _print_helper(const v8::FunctionCallbackInfo<v8::Value>& args, bool append_newline) {
148  std::stringstream sstream;
149  auto values = get_all_values(args);
150  int i = 0;
151  for (auto value : values) {
152  if (i > 0) {
153  sstream << " ";
154  }
155  sstream << *v8::String::Utf8Value(value);
156  i++;
157  }
158  if (append_newline) {
159  sstream << std::endl;
160  }
161  return sstream.str();
162 }
163 
164 
165 
166 void add_print(v8::Isolate * isolate, const v8::Local<v8::ObjectTemplate> object_template, func::function<void(const std::string &)> callback) {
167  add_function(isolate, object_template, "printf", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_printf_helper(info, false));});
168  add_function(isolate, object_template, "printfln", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_printf_helper(info, true));});
169  add_function(isolate, object_template, "sprintf", [](const v8::FunctionCallbackInfo<v8::Value>& info){return _format_helper(info, false);});
170 
171  add_function(isolate, object_template, "print", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_print_helper(info, false));});
172  add_function(isolate, object_template, "println", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_print_helper(info, true));});
173 
174  add_function(isolate, object_template, "printobj", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){
175  auto isolate = info.GetIsolate();
176  for (int i = 0; i < info.Length(); i++) {
177  callback(stringify_value(isolate, info[i]) + "\n");
178  }
179  });
180  add_function(isolate, object_template, "printobjall", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){
181  auto isolate = info.GetIsolate();
182  for (int i = 0; i < info.Length(); i++) {
183  callback(stringify_value(isolate, info[i], true) + "\n");
184  }
185  });
186 
187 }
188 
189 void add_print(const v8::Local<v8::Context> context, func::function<void(const std::string &)> callback) {
190  add_function(context, context->Global(), "printf", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_printf_helper(info, false));});
191  add_function(context, context->Global(), "printfln", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_printf_helper(info, true));});
192  add_function(context, context->Global(), "sprintf", [](const v8::FunctionCallbackInfo<v8::Value>& info){return _format_helper(info, false);});
193 
194  add_function(context, context->Global(), "print", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_print_helper(info, false));});
195  add_function(context, context->Global(), "println", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){callback(_print_helper(info, true));});
196 
197  add_function(context, context->Global(), "printobj", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){
198  auto isolate = info.GetIsolate();
199  for (int i = 0; i < info.Length(); i++) {
200  callback(stringify_value(isolate, info[i]) + "\n");
201  }
202  });
203  add_function(context, context->Global(), "printobjall", [callback](const v8::FunctionCallbackInfo<v8::Value>& info){
204  auto isolate = info.GetIsolate();
205  for (int i = 0; i < info.Length(); i++) {
206  callback(stringify_value(isolate, info[i], true) + "\n");
207  }
208  });
209 }
210 
211 
212 void add_assert(v8::Isolate * isolate, v8::Local<v8::ObjectTemplate> object_template)
213 {
214  add_function(isolate, object_template, "assert", [](const v8::FunctionCallbackInfo<v8::Value>& info) {
215  auto isolate = info.GetIsolate();
216  auto context = isolate->GetCurrentContext();
217  if (V8_TOOLKIT_DEBUG) printf("Asserting: '%s'\n", *v8::String::Utf8Value(info[0]));
218 
219  v8::TryCatch tc(isolate);
220  auto script_maybe = v8::Script::Compile(context, info[0]->ToString());
221  assert(!tc.HasCaught());
222 
223  auto script = script_maybe.ToLocalChecked();
224  auto result_maybe = script->Run(context);
225  assert (!tc.HasCaught());
226 
227  auto result = result_maybe.ToLocalChecked();
228  // print_v8_value_details(result);
229 
230  bool default_value = false;
231  bool assert_result = result->BooleanValue(context).FromMaybe(default_value);
232 // print_v8_value_details(result);
233  assert(assert_result);
234  });
235 }
236 
237 
238 
239 bool get_file_contents(std::string filename, std::string & file_contents)
240 {
241  time_t ignored_time;
242  return get_file_contents(filename, file_contents, ignored_time);
243 }
244 
245 #ifdef _MSC_VER
246 #include <windows.h>
247 #endif
248 
249 bool get_file_contents(std::string filename, std::string & file_contents, time_t & file_modification_time)
250 {
251 #ifdef _MSC_VER
252  auto file_handle = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
253  if (file_handle == INVALID_HANDLE_VALUE) {
254  return false;
255  }
256 
257  FILETIME creationTime,
258  lpLastAccessTime,
259  lastWriteTime;
260  bool err = GetFileTime(file_handle, &creationTime, &lpLastAccessTime, &lastWriteTime);
261 
262  auto file_size = GetFileSize(file_handle, nullptr);
263  file_contents.resize(file_size);
264 
265  ReadFile(file_handle, &file_contents[0], file_size, nullptr, nullptr);
266 
267 
268 #else
269 
270 
271 
272  int fd = open(filename.c_str(), O_RDONLY);
273  if (fd == -1) {
274  return false;
275  }
276  struct stat file_stat;
277  if (fstat(fd, &file_stat) == -1) {
278  return false;
279  }
280 
281  file_modification_time = file_stat.st_mtime;
282  auto file_size = file_stat.st_size;
283 
284  file_contents.resize(file_size);
285 
286  auto bytes_remaining = file_size;
287  // TODO: Need to handle file not having as many bytes as expected with open returning 0 before bytes_remaining == 0
288  while (bytes_remaining > 0) {
289  auto bytes_read = read(fd, &file_contents[file_size - bytes_remaining], bytes_remaining);
290  bytes_remaining -= bytes_read;
291  }
292 
293  return true;
294 #endif
295 }
296 
297 bool _get_modification_time_of_filename(std::string filename, time_t & modification_time)
298 {
299 
300 #if defined _MSC_VER
301  auto file_handle = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
302  if (file_handle == INVALID_HANDLE_VALUE) {
303  return false;
304  }
305 
306  FILETIME creationTime,
307  lpLastAccessTime,
308  lastWriteTime;
309  bool err = GetFileTime(file_handle, &creationTime, &lpLastAccessTime, &lastWriteTime);
310 
311  modification_time = *(time_t*)&lastWriteTime;
312 
313  return true;
314 
315 #else
316  int fd = open(filename.c_str(), O_RDONLY);
317  if (fd == -1) {
318  return false;
319  }
320  struct stat file_stat;
321  if (fstat(fd, &file_stat) == -1) {
322  return false;
323  }
324 
325  modification_time = file_stat.st_mtime;
326  return true;
327 #endif
328 }
329 
330 
331 
332 // once a module has been successfully required (in an isolate), its return value will be cached here
333 // and subsequent requires of the same module won't re-run the module, just return the
334 // cached value
336 
337 typedef std::map<std::string, RequireResult> cached_isolate_modules_t; // a named module result
338 static std::map<v8::Isolate *, cached_isolate_modules_t> require_results;
339 
340 void delete_require_cache_for_isolate(v8::Isolate * isolate) {
341  std::lock_guard<std::mutex> l(require_results_mutex);
342  require_results.erase(isolate);
343 }
344 
345 cached_isolate_modules_t & get_loaded_modules(v8::Isolate * isolate)
346 {
347  std::lock_guard<std::mutex> l(require_results_mutex);
348  return require_results[isolate];
349 }
350 
351 
352 /**
353 * Takes in javascript source and attempts to compile it to a script.
354 * On error, it sets the output parameter `error` and returns `false`
355 */
356 bool compile_source(v8::Local<v8::Context> & context, std::string source, v8::Local<v8::Script> & script, v8::Local<v8::Value> & error, v8::ScriptOrigin * script_origin = nullptr)
357 {
358  auto isolate = context->GetIsolate();
359  v8::TryCatch try_catch(isolate);
360  auto local_source = v8::String::NewFromUtf8(isolate, source.c_str());
361 
362  auto script_maybe = v8::Script::Compile(context, local_source, script_origin);
363 
364  if (try_catch.HasCaught()) {
365 
366  ReportException(isolate, &try_catch);
367 
368  // TODO: Is this the rignt thing to do? Can this function be called from within a javascript context? Maybe for assert()?
369  error = try_catch.Exception();
370  printf("%s\n", stringify_value(isolate, try_catch.Exception()).c_str());
371  if (V8_TOOLKIT_DEBUG) printf("Failed to compile: %s\n", *v8::String::Utf8Value(try_catch.Exception()));
372  return false;
373  }
374 
375  script = script_maybe.ToLocalChecked();
376  return true;
377 }
378 
379 
381 
382  v8::Isolate * isolate = context->GetIsolate();
383 
384  // This catches any errors thrown during script compilation
385  v8::TryCatch try_catch(isolate);
386 
387  auto maybe_result = script->Run(context);
388  if (try_catch.HasCaught()) {
389 // printf("Context::run threw exception - about to print details:\n");
390  ReportException(isolate, &try_catch);
391  } else {
392 // printf("Context::run ran without throwing exception\n");
393  }
394 
395  if(maybe_result.IsEmpty()) {
396 
397  v8::Local<v8::Value> e = try_catch.Exception();
398  // print_v8_value_details(e);
399 
400 
401  // This functionality causes the javascript to not be able to catch and understand the exception
402  if(e->IsExternal()) {
403  auto anybase = (AnyBase *)v8::External::Cast(*e)->Value();
404  auto anyptr_exception_ptr = dynamic_cast<Any<std::exception_ptr> *>(anybase);
405  assert(anyptr_exception_ptr); // cannot handle other types at this time TODO: throw some other type of exception if this happens UnknownExceptionException or something
406 
407  // TODO: Are we leaking a copy of this exception by not cleaning up the exception_ptr ref count?
408  std::rethrow_exception(anyptr_exception_ptr->get());
409  } else {
410  printf("v8 internal exception thrown: %s\n", *v8::String::Utf8Value(e));
411  throw V8Exception(isolate, v8::Global<v8::Value>(isolate, e));
412  }
413  }
414  v8::Local<v8::Value> result = maybe_result.ToLocalChecked();
415  return result;
416 }
417 
418 
419 
420 /**
421  * Compiles the given code within a containing function and returns the exports object
422  * @param context
423  * @param module_source
424  * @param script_origin
425  * @return
426  */
428  const std::string module_source,
429  const v8::ScriptOrigin & script_origin,
430  v8::Local<v8::Function> & compiled_function) {
431 
432  auto isolate = context->GetIsolate();
433 
434 
435 // std::cerr << fmt::format("ScriptOrigin.ResourceName: {}, line offset: {}, column offset: {}, source: {}",
436 // *v8::String::Utf8Value(script_origin.ResourceName()), script_origin.ResourceLineOffset()->Value(),
437 // script_origin.ResourceColumnOffset()->Value(), module_source.c_str()) << std::endl;
438 
439 
440  v8::ScriptCompiler::Source source(v8::String::NewFromUtf8(isolate, module_source.c_str()), script_origin);
441  v8::Local<v8::String> parameter_names[] = {
442  "module"_v8, "exports"_v8
443  };
444  v8::TryCatch try_catch(isolate);
445  auto maybe_module_function =
446  v8::ScriptCompiler::CompileFunctionInContext(context, &source, 2, &parameter_names[0], 0, nullptr);
447  if (try_catch.HasCaught()) {
448  ReportException(isolate, &try_catch);
449  assert(false);
450  }
451 
452 
453 
454  // NEED PROPER ERROR HANDLING HERE
455  assert(!maybe_module_function.IsEmpty());
456  compiled_function = maybe_module_function.ToLocalChecked();
457 
458 // std::cerr << fmt::format("module script id: {}", compiled_function->GetScriptOrigin().ScriptID()->Value()) << std::endl;
459 // std::cerr << fmt::format("After CompileFunctionInContext: ScriptOrigin.ResourceName: {}, line offset: {}, column offset: {}, source: {}",
460 // *v8::String::Utf8Value(compiled_function->GetScriptOrigin().ResourceName()), compiled_function->GetScriptOrigin().ResourceLineOffset()->Value(),
461 // compiled_function->GetScriptOrigin().ResourceColumnOffset()->Value(), module_source.c_str()) << std::endl;
462 
463 
464  v8::Local<v8::Object> receiver = v8::Object::New(isolate);
465  v8::Local<v8::Value> module_params[2];
466  module_params[0] = v8::Object::New(isolate);
467  auto exports_object = v8::Object::New(isolate);
468  module_params[1] = exports_object;
469  add_variable(context, module_params[0]->ToObject(), "exports", exports_object);
470 
471  (void)compiled_function->Call(context, context->Global(), 2, &module_params[0]);
472 
473  return exports_object;
474 }
475 
476 
477 
478 /** Attempts to load the specified external resource.
479 * Attempts to load exact match filename in each path, then <filename>.js in each path, then <filename>.json in each path
480 *
481 * Paths are attempted in vector order[0, 1, 2...]. If the matching filename ends in .js (either because it was specified as such or because
482 * it matched after the suffix was added) it will be executed as a traditional module with the exports object being returned. If the matching
483 * filename ends in .json, the last value in the file will be returned.
484 * The results of require'd files is cached and if the same module (based on the full matching value including path and any added suffix) is
485 * required again, the cached value will be returned. The module will not be re-run.
486 *
487 * The goal of this function is to be as close to node.js require as possible, so patches or descriptions of how it differs are appreciated.
488 * Not that much time was spent trying to determine the exact behavior, so there are likely significant differences
489 */
490 //#define REQUIRE_DEBUG_PRINTS false
491 //#define REQUIRE_DEBUG_PRINTS true
492 bool require(
493  v8::Local<v8::Context> context,
494  std::string filename,
495  v8::Local<v8::Value> & result,
496  const std::vector<std::string> & paths,
497  bool track_file_modification_times,
498  bool use_cache,
499  func::function<void(RequireResult const &)> callback,
500  func::function<std::string(std::string const &)> resource_name_callback)
501 {
502 
503  auto isolate = context->GetIsolate();
504  v8::Locker l(isolate);
505  if (filename.find("..") != std::string::npos) {
506  if (REQUIRE_DEBUG_PRINTS) printf("require() attempted to use a path with more than one . in a row '%s' (disallowed as simple algorithm to stop tricky paths)", filename.c_str());
507  isolate->ThrowException("Cannot specify a file containing .."_v8);
508  return false;
509  }
510 
511  for (auto suffix : std::vector<std::string>{"", ".js", ".json", }) {
512  for (auto path : paths) {
513  try {
514 #ifdef _MSC_VER
515  auto complete_filename = path + "\\" + filename + suffix;
516 #else
517  auto complete_filename = path + "/" + filename + suffix;
518 #endif
519 
520  std::string resource_name = complete_filename;
521  if (resource_name_callback) {
522  resource_name = resource_name_callback(complete_filename);
523  }
524 
525  std::string file_contents;
526  time_t file_modification_time = 0;
527  if (!get_file_contents(complete_filename, file_contents, file_modification_time)) {
528  if (REQUIRE_DEBUG_PRINTS) printf("Module not found at %s\n", complete_filename.c_str());
529  continue;
530  }
531 
532  // get the map of cached results for this isolate (guaranteed to exist because it was created before this lambda)
533  // This is read-only and per-isolate and the only way to add is to be in the isolate
534  if (use_cache) {
535  std::lock_guard<std::mutex> l(require_results_mutex);
536  auto & isolate_require_results = require_results[isolate];
537 
538  auto cached_require_results = isolate_require_results.find(complete_filename);
539  if (cached_require_results != isolate_require_results.end()) {
540  if (REQUIRE_DEBUG_PRINTS) printf("Found cached results, using cache instead of re-running module\n");
541 
542  // if we don't care about file modifications or the file modification time is the same as before,
543  // return the cached result
544  if (!track_file_modification_times || file_modification_time == cached_require_results->second.time) {
545  if (REQUIRE_DEBUG_PRINTS) printf("Returning cached results\n");
546  result = cached_require_results->second.result.Get(isolate);
547  return true;
548  } else {
549  if (REQUIRE_DEBUG_PRINTS) printf("Not returning cached results because modification time was no good\n");
550  }
551  } else {
552  if (REQUIRE_DEBUG_PRINTS) printf("Didn't find cached version for isolate %p %s\n", isolate, complete_filename.c_str());
553  }
554  }
555 
556  // CACHE WAS SEARCHED AND NO RESULT FOUND - DO THE WORK AND CACHE THE RESULT AFTERWARDS
557 
558  // Compile the source code.
559 
560  v8::Local<v8::Script> script;
561 
562  if (std::regex_search(filename, std::regex(".json$"))) {
563 
564  v8::ScriptOrigin script_origin(v8::String::NewFromUtf8(isolate,
565  resource_name.c_str()),
566  v8::Integer::New(isolate, 0), // line offset
567  v8::Integer::New(isolate, 0) // column offset
568  );
569 
570  v8::Local<v8::Value> error;
571  v8::TryCatch try_catch(isolate);
572  // TODO: make sure requiring a json file is being tested
573  if (REQUIRE_DEBUG_PRINTS) printf("About to try to parse json: %s\n", file_contents.c_str());
574  auto maybe_result = v8::JSON::Parse(isolate, v8::String::NewFromUtf8(isolate,file_contents.c_str()));
575  if (try_catch.HasCaught()) {
576  try_catch.ReThrow();
577  if (REQUIRE_DEBUG_PRINTS) printf("Couldn't run json for %s, error: %s\n", complete_filename.c_str(), *v8::String::Utf8Value(try_catch.Exception()));
578  return false;
579  }
580  result = maybe_result.ToLocalChecked();
581 
582  // cache the result for subsequent requires of the same module in the same isolate
583  if (use_cache) {
584  std::lock_guard<std::mutex> l(require_results_mutex);
585  auto & isolate_require_results = require_results[isolate];
586  auto i = isolate_require_results.find(complete_filename);
587  if (i == isolate_require_results.end()) {
588  isolate_require_results.emplace(complete_filename, RequireResult(isolate, context, v8::Local<v8::Function>(), result, time(nullptr)));
589  }
590  }
591 
592  } else {
593 
594  v8::ScriptOrigin script_origin(v8::String::NewFromUtf8(isolate, resource_name.c_str()),
595  0_v8, // line offset
596  26_v8, // column offset - cranked up because v8 subtracts a bunch off but if it's negative then chrome ignores it
598 
599  v8::Local<v8::Value> error;
600  v8::Local<v8::Function> module_function;
601 
602  result = execute_module(context, file_contents, script_origin, module_function);
603 
604  std::lock_guard<std::mutex> l(require_results_mutex);
605  auto & isolate_require_results = require_results[isolate];
606  isolate_require_results.emplace(complete_filename,
607  RequireResult(isolate, context, module_function, result,
608  file_modification_time));
609  if (callback) {
610  callback(isolate_require_results.find(complete_filename)->second);
611  }
612 
613 
614 
615  }
616 
617 
618 
619  // printf("Require final result: %s\n", stringify_value(isolate, result).c_str());
620  // printf("Require returning resulting object for module %s\n", complete_filename.c_str());
621  return true;
622  }catch(...) {}
623  // if any failures, try the next path if it exists
624  }
625  }
626  if (REQUIRE_DEBUG_PRINTS) printf("Couldn't find any matches for %s\n", filename.c_str());
627  isolate->ThrowException("No such module found in any search path"_v8);
628  return false;
629 }
630 
631 
632 void add_module_list(v8::Isolate * isolate, const v8::Local<v8::ObjectTemplate> & object_template)
633 {
634  using ReturnType = std::map<std::string, v8::Global<v8::Value>&>;
635 
636  add_function(isolate, object_template, "module_list",
637 
638  [isolate]{return scoped_run(isolate, [isolate]()->ReturnType {
639  ReturnType results;
640  auto & isolate_results = require_results[isolate];
641  for (auto & result_pair : isolate_results) {
642  results.emplace(result_pair.first, result_pair.second.result);
643  }
644  return results;
645  });
646  });
647 }
648 
649 
650 void add_require(v8::Isolate * isolate, const v8::Local<v8::ObjectTemplate> & object_template, const std::vector<std::string> & paths) {
651 
652  // if there's no entry for cached results for this isolate, make one now
653  {
654  std::lock_guard<std::mutex> guard(require_results_mutex);
655  if (require_results.find(isolate) == require_results.end()) {
656  require_results.insert(make_pair(isolate, cached_isolate_modules_t()));
657  }
658  }
659 
660  (void)add_function(isolate, object_template, "require",
661  [isolate, paths](const std::string & filename) {
662  auto context = isolate->GetCurrentContext();
663  v8::Local<v8::Value> result;
664 
665  // if require returns false, it will throw a javascript exception
666  // so it doesn't matter if the result sent back is good
667  if(require(context, filename, result, paths)) {
668  if (V8_TOOLKIT_DEBUG) printf("Require returning to caller: '%s'\n", stringify_value(isolate, result).c_str());
669  }
670  return result;
671  });
672 }
673 
674 
675 // to find the directory of the executable, you could use argv[0], but here are better results:
676 // http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe/1024937#1024937
677 // including these as helpers in this library would probably be useful
679 {
680  // This probably works on more than just APPLE
681 #ifdef __APPLE__
682  auto full_directory_name = directory_name;
683  DIR * dir = opendir(full_directory_name.c_str());
684  if (dir == NULL) {
685  if (V8_TOOLKIT_DEBUG) printf("Could not open directory %s\n", full_directory_name.c_str());
686  return;
687  }
688  struct dirent * dp;
689 
690  auto require_path = std::vector<std::string>{directory_name};
691  while ((dp = readdir(dir)) != NULL) {
692  if (dp->d_type == DT_DIR) {
693  // printf("Skipping %s because it's a directory\n", dp->d_name);
694  continue;
695  }
696  if (V8_TOOLKIT_DEBUG) printf("reading directory, got %s\n", dp->d_name);
697  v8::Local<v8::Value> result;
698  require(context, dp->d_name, result, require_path);
699  }
700  if (V8_TOOLKIT_DEBUG) printf("Done reading directory\n");
701  (void)closedir(dir);
702  return;
703 
704 #endif // __APPLE__
705 
706  assert(false);
707 
708 }
709 
710 
711 void dump_prototypes(v8::Isolate * isolate, v8::Local<v8::Object> object)
712 {
713  fprintf(stderr, "Looking at prototype chain\n");
714  while (!object->IsNull()) {
715  /* This code assumes things about what is in the internfieldcount that isn't safe to assume
716  if (object->InternalFieldCount() == 1) {
717  auto wrap = v8::Local<v8::External>::Cast(object->GetInternalField(0));
718  // the type of the class wrapper doesn't matter - it's just a pointer
719  auto wrapped_data = static_cast<WrappedData<int> *>(wrap->Value());
720  fprintf(stderr, "Prototype is wrapped object with debug string type name: %s\n", wrapped_data->native_object_type.c_str());
721  }
722  */
723  fprintf(stderr, "%s:\n", *v8::String::Utf8Value(object));
724  // print_v8_value_details(foo);
725  fprintf(stderr, "%s\n", stringify_value(isolate, object).c_str());
726  object = v8::Local<v8::Object>::Cast(object->GetPrototype());
727  }
728  fprintf(stderr, "Done looking at prototype chain\n");
729 }
730 
731 
732 bool compare_contents(v8::Isolate * isolate, const v8::Local<v8::Value> & left, const v8::Local<v8::Value> & right)
733 {
734  // printf("Comparing two things\n");
735  auto context = isolate->GetCurrentContext();
736 
737  // if they're both undefined, then they are equal. If only one, then they're not.
738  if (left->IsUndefined() || right->IsUndefined()) {
739  return left->IsUndefined() && right->IsUndefined();
740  }
741 
742  v8::Local<v8::Boolean> bool_left;
743  v8::Local<v8::Boolean> bool_right;
744 
745  // if the left is a bool, return true if right is a bool and they match, otherwise false
746  if (left->IsBoolean()) {
747  // printf("Checking two booleans\n");
748  return right->IsBoolean() && left->ToBoolean(context).ToLocalChecked()->Value() == right->ToBoolean(context).ToLocalChecked()->Value();
749  }
750 
751  if (left->IsNumber()) {
752  // printf("Checking two numbers\n");
753  // auto ln = left->ToNumber(context).ToLocalChecked()->Value();
754  // auto rn = right->ToNumber(context).ToLocalChecked()->Value();
755  // printf("Comparing %f and %f\n", ln, rn);
756  return right->IsNumber() && left->ToNumber(context).ToLocalChecked()->Value() == right->ToNumber(context).ToLocalChecked()->Value();
757  }
758 
759  if (left->IsString()) {
760  // printf("Checking two strings\n");
761  if (!right->IsString()) {
762  return false;
763  }
764  return !strcmp(*v8::String::Utf8Value(left), *v8::String::Utf8Value(right));
765  }
766 
767  if (left->IsArray()) {
768  // printf("Checking two arrays\n");
769  if (!right->IsArray()) {
770  return false;
771  }
772  auto array_left = v8::Local<v8::Array>::Cast(left);
773  auto array_right = v8::Local<v8::Array>::Cast(right);
774 
775  auto left_length = get_array_length(isolate, array_left);
776  auto right_length = get_array_length(isolate, array_right);
777 
778  if (left_length != right_length) {
779  // printf("Array lengths differ %d %d\n", (int)left_length, (int) right_length);
780  return false;
781  }
782 
783  for (int i = 0; i < left_length; i++) {
784  auto left_value = array_left->Get(context, i);
785  auto right_value = array_right->Get(context, i);
786  if (!compare_contents(isolate, left_value.ToLocalChecked(), right_value.ToLocalChecked())) {
787  return false;
788  }
789  }
790  return true;
791  }
792 
793 
794  // check this last in case it's some other type of more specialized object we will test the specialization instead (like an array)
795  // objects must have all the same keys and each key must have the same as determined by calling this function on each value
796  auto object_left = v8::Local<v8::Object>::Cast(left);
797  if(!object_left.IsEmpty()) {
798  // printf("Checking two arrays\n");
799 
800  auto object_right = v8::Local<v8::Object>::Cast(right);
801 
802  // if they're not both objects, return false
803  if (object_right.IsEmpty()) {
804  // printf("right value not object\n");
805  return false;
806  }
807  auto left_keys = make_set_from_object_keys(isolate, object_left);
808  auto right_keys = make_set_from_object_keys(isolate, object_right);
809  if (left_keys.size() != right_keys.size()) {
810  // printf("key count mismatch: %d %d\n", (int)left_keys.size(), (int)right_keys.size());
811  return false;
812  }
813 
814  for(auto left_key : left_keys) {
815  auto left_value = object_left->Get(context, v8::String::NewFromUtf8(isolate, left_key.c_str()));
816  auto right_value = object_right->Get(context, v8::String::NewFromUtf8(isolate, left_key.c_str()));
817  if (right_value.IsEmpty()) {
818  // printf("right side doesn't have key: %s\n", left_key.c_str());
819  return false;
820  } else if (!compare_contents(isolate, left_value.ToLocalChecked(), right_value.ToLocalChecked())) {
821  // printf("Recursive check of value in both objects returned false for key %s\n", left_key.c_str());
822  return false;
823  }
824  }
825 
826  return true;
827  }
828  // printf("Returning false because left value is of unknown/unhandled type\n");
829 
830  return false;
831 }
832 
833 
834 
835 
836 
838 
839 
840 /*
841 * "interesting" means things not a part of Object - or at least pretty close to that
842 */
844 {
845  auto isolate = context->GetIsolate();
846  auto names = object->GetPropertyNames(context).ToLocalChecked();
847  return CastToNative<std::vector<std::string>>()(isolate, names);
848 }
849 
850 
851 
852 SetWeakCallbackData::SetWeakCallbackData(func::function<void(v8::WeakCallbackInfo<SetWeakCallbackData> const &)> callback,
853  v8::Isolate * isolate,
854  const v8::Local<v8::Object> & javascript_object, bool destructive) :
855  callback(callback),
856  destructive(destructive)
857  {
858 // std::cerr << fmt::format("Creating weak callback data with destructive: {}", this->destructive) << std::endl;
859  this->global.Reset(isolate, javascript_object);
860  }
861 
862 
863 SetWeakCallbackData * global_set_weak(v8::Isolate * isolate,
864  const v8::Local<v8::Object> & javascript_object,
865  func::function<void(v8::WeakCallbackInfo<SetWeakCallbackData> const &)> callback, bool destructive)
866 {
867  // this memory deleted in the GC callback
868  auto callback_data = new SetWeakCallbackData(callback, isolate, javascript_object, destructive);
869 
870  // set the callback on the javascript_object to be called when it's garbage collected
871  callback_data->global.template SetWeak<SetWeakCallbackData>(callback_data,
872  [](const v8::WeakCallbackInfo<SetWeakCallbackData> & info) {
873  SetWeakCallbackData * callback_data = info.GetParameter();
874  callback_data->callback(info);
875  callback_data->global.Reset();
876  delete callback_data; // delete the memory allocated when global_set_weak is called
877  }, v8::WeakCallbackType::kParameter);
878 
879  return callback_data;
880 }
881 
882 
883 
884 
885 void foreach_filesystem_helper(const std::string & directory_name,
886  const volatile bool files,
887  const volatile bool directories,
888  std::function<void(const std::string &)> const & callback)
889 {
890  // This probably works on more than just APPLE
891 #ifndef _MSC_VER
892 
893  auto full_directory_name = directory_name;
894  DIR * dir = opendir(full_directory_name.c_str());
895  if (dir == NULL) {
896  printf("Could not open directory %s\n", full_directory_name.c_str());
897  return;
898  }
899  struct dirent * dp;
900  while ((dp = readdir(dir)) != NULL) {
901 
902  if ((dp->d_type == DT_DIR && directories && strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) ||
903  (dp->d_type == DT_REG && files)) {
904  callback(dp->d_name);
905  }
906  }
907  (void)closedir(dir);
908  return;
909 
910 #else
911 
912  WIN32_FIND_DATA file_metadata;
913  HANDLE directory = FindFirstFile((directory_name + "\\*").c_str(), &file_metadata);
914 
915  if (directory == INVALID_HANDLE_VALUE) {
916  printf("Could not open directory %s\n", directory_name.c_str());
917  return;
918  }
919  do {
920  if (!strcmp(file_metadata.cFileName, ".") || !strcmp(file_metadata.cFileName, "..")) {
921  continue;
922  }
923  if (
924  ((file_metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && directories) ||
925  ((!(file_metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) && files)) {
926  callback(file_metadata.cFileName);
927  } else {
928  printf("Skipping file %s because it's not of desired type file: %d directory: %d\n", file_metadata.cFileName, files, directories);
929  continue;
930  }
931 
932 
933  } while(FindNextFile(directory, &file_metadata));
934 
935  FindClose(directory);
936  return;
937 
938 
939 #endif
940 
941 }
942 
943 void foreach_file(const std::string & directory_name, std::function<void(const std::string &)> const & callback) {
944  return foreach_filesystem_helper(directory_name, true, false, callback);
945 }
946 
947 void foreach_directory(const std::string & directory_name, std::function<void(const std::string &)> const & callback) {
948  return foreach_filesystem_helper(directory_name, false, true, callback);
949 }
950 
951 
952 
953 } // namespace v8toolkit
954 
955 
956 
957 
std::set< std::string > make_set_from_object_keys(v8::Isolate *isolate, v8::Local< v8::Object > &object, bool own_properties_only=true)
Definition: v8helpers.cpp:160
std::string get_stack_trace_string(v8::Local< v8::StackTrace > stack_trace)
Definition: v8helpers.cpp:28
std::map< std::string, RequireResult > cached_isolate_modules_t
Definition: v8toolkit.cpp:337
SetWeakCallbackData(func::function< void(v8::WeakCallbackInfo< SetWeakCallbackData > const &)> callback, v8::Isolate *isolate, const v8::Local< v8::Object > &javascript_object, bool destructive)
Definition: v8toolkit.cpp:852
InvalidCallException(const std::string &message)
Definition: v8toolkit.cpp:59
std::string _printf_helper(const v8::FunctionCallbackInfo< v8::Value > &args, bool append_newline)
Definition: v8toolkit.cpp:116
void expose_gc()
Definition: v8toolkit.cpp:52
v8::Local< v8::Value > run_script(v8::Local< v8::Context > context, v8::Local< v8::Script > script)
Definition: v8toolkit.cpp:380
std::vector< std::string > get_interesting_properties(v8::Local< v8::Context > context, v8::Local< v8::Object > object)
Definition: v8toolkit.cpp:843
std::string _print_helper(const v8::FunctionCallbackInfo< v8::Value > &args, bool append_newline)
Definition: v8toolkit.cpp:147
::std::string string
Definition: gtest-port.h:1097
void add_assert(v8::Isolate *isolate, v8::Local< v8::ObjectTemplate > object_template)
Definition: v8toolkit.cpp:212
virtual ~AnyBase()
Definition: v8toolkit.cpp:837
int get_array_length(v8::Isolate *isolate, v8::Local< v8::Value > array_value)
Definition: v8helpers.cpp:85
#define V8_TOOLKIT_DEBUG
Definition: v8toolkit.h:24
bool compile_source(v8::Local< v8::Context > &context, std::string source, v8::Local< v8::Script > &script, v8::Local< v8::Value > &error, v8::ScriptOrigin *script_origin=nullptr)
Definition: v8toolkit.cpp:356
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
void add_require(v8::Isolate *isolate, const v8::Local< v8::ObjectTemplate > &context, const std::vector< std::string > &paths)
Definition: v8toolkit.cpp:650
cached_isolate_modules_t & get_loaded_modules(v8::Isolate *isolate)
Definition: v8toolkit.cpp:345
GTEST_API_ size_t GetFileSize(FILE *file)
void delete_require_cache_for_isolate(v8::Isolate *isolate)
Definition: v8toolkit.cpp:340
v8::Local< v8::Value > execute_module(v8::Local< v8::Context > context, const std::string module_source, const v8::ScriptOrigin &script_origin, v8::Local< v8::Function > &compiled_function)
Definition: v8toolkit.cpp:427
Definition: sample.cpp:29
SetWeakCallbackData * global_set_weak(v8::Isolate *isolate, const v8::Local< v8::Object > &javascript_object, func::function< void(v8::WeakCallbackInfo< SetWeakCallbackData > const &)> callback, bool destructive)
Definition: v8toolkit.cpp:863
void dump_prototypes(v8::Isolate *isolate, v8::Local< v8::Object > object)
Definition: v8toolkit.cpp:711
void add_module_list(v8::Isolate *isolate, const v8::Local< v8::ObjectTemplate > &object_template)
Definition: v8toolkit.cpp:632
bool compare_contents(v8::Isolate *isolate, const v8::Local< v8::Value > &left, const v8::Local< v8::Value > &right)
Definition: v8toolkit.cpp:732
v8::Local< v8::FunctionTemplate > make_function_template(v8::Isolate *isolate, func::function< R(Args...)> f, std::string const &name)
Definition: v8toolkit.h:356
std::string _format_helper(const v8::FunctionCallbackInfo< v8::Value > &args, bool append_newline)
Definition: v8toolkit.cpp:84
auto scoped_run(v8::Isolate *isolate, T callable) -> typename std::result_of< T()>::type
Definition: v8toolkit.h:111
void foreach_directory(const std::string &directory_name, std::function< void(const std::string &)> const &callback)
Definition: v8toolkit.cpp:947
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
bool _get_modification_time_of_filename(std::string filename, time_t &modification_time)
Definition: v8toolkit.cpp:297
void foreach_filesystem_helper(const std::string &directory_name, const volatile bool files, const volatile bool directories, std::function< void(const std::string &)> const &callback)
Definition: v8toolkit.cpp:885
void ReportException(v8::Isolate *isolate, v8::TryCatch *try_catch)
Definition: v8helpers.cpp:316
static std::map< v8::Isolate *, cached_isolate_modules_t > require_results
Definition: v8toolkit.cpp:338
v8::Global< v8::Object > global
Definition: v8toolkit.h:48
void foreach_file(const std::string &directory_name, std::function< void(const std::string &)> const &callback)
Definition: v8toolkit.cpp:943
void add_function(v8::Isolate *isolate, const v8::Local< v8::ObjectTemplate > &object_template, const char *name, func::function< R(Args...)> function)
Definition: v8toolkit.h:431
func::function< void(v8::WeakCallbackInfo< SetWeakCallbackData > const &)> callback
Definition: v8toolkit.h:47
void process_v8_flags(int &argc, char **argv)
Definition: v8toolkit.cpp:46
void require_directory(v8::Local< v8::Context > context, std::string directory_name)
Definition: v8toolkit.cpp:678
std::mutex require_results_mutex
Definition: v8toolkit.cpp:335
std::map< v8::Isolate *, std::vector< std::string > > used_constructor_name_list_map
Definition: v8toolkit.cpp:39
void add_variable(v8::Isolate *isolate, const v8::Local< v8::ObjectTemplate > &object_template, const char *name, const v8::Local< v8::Data > value)
Definition: v8toolkit.cpp:65
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
std::vector< v8::Local< v8::Value > > get_all_values(const v8::FunctionCallbackInfo< v8::Value > &args, int depth=1)
Definition: v8toolkit.cpp:123
V8ClassWrapperInstanceRegistry wrapper_registery
Definition: v8toolkit.cpp:42