v8toolkit  0.0.1
Utility library for embedding V8 Javascript engine in a c++ program
v8helpers.cpp
Go to the documentation of this file.
1 #include <assert.h>
2 #include <sstream>
3 #include <set>
4 #include <v8toolkit.h>
5 
6 #include "v8helpers.h"
7 
8 namespace v8toolkit {
9 
10 
11 bool AnybaseDebugPrintFlag = false;
12 
13 
16  StdFunctionCallbackType const & callback) :
17  method_name(method_name),
18  callback(callback)
19 {}
20 
21 
22 
23 /**
24 * Returns a string with the given stack trace and a leading and trailing newline
25 * @param stack_trace stack trace to return a string representation of
26 * @return string representation of the given stack trace
27 */
29  std::stringstream result;
30  result << std::endl;
31  int frame_count = stack_trace->GetFrameCount();
32  for (int i = 0; i < frame_count; i++) {
33  auto frame = stack_trace->GetFrame(i);
34  result << fmt::format("{}:{} {}",
35  *v8::String::Utf8Value(frame->GetScriptName()),
36  frame->GetLineNumber(),
37  *v8::String::Utf8Value(frame->GetFunctionName())) << std::endl;
38  }
39  return result.str();
40 }
41 
42 
43 
44 
46 
47 #ifdef V8TOOLKIT_DEMANGLE_NAMES
48 // printf("Starting name demangling\n");
49  std::string result;
50  int status;
51  auto demangled_name_needs_to_be_freed = abi::__cxa_demangle(mangled_name.c_str(), nullptr, 0, &status);
52  result = demangled_name_needs_to_be_freed;
53 
54  if (demangled_name_needs_to_be_freed == nullptr) {
55  return mangled_name;
56  }
57 
58  if (status == 0) {
59  result = demangled_name_needs_to_be_freed;
60  } else {
61  // https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html
62  //-1: A memory allocation failiure occurred.
63  //-2: mangled_name is not a valid name under the C++ ABI mangling rules.
64  //-3: One of the arguments is invalid.
65  result = mangled_name;
66  }
67  if (demangled_name_needs_to_be_freed) {
68  free(demangled_name_needs_to_be_freed);
69  }
70  return result;
71 
72 #else
73  return mangled_name;
74 #endif
75 }
76 
77 using namespace literals;
78 
79 int get_array_length(v8::Isolate * isolate, v8::Local<v8::Array> array) {
80  auto context = isolate->GetCurrentContext();
81  return array->Get(context, "length"_v8).ToLocalChecked()->Uint32Value();
82 }
83 
84 
85 int get_array_length(v8::Isolate * isolate, v8::Local<v8::Value> array_value)
86 {
87  if(array_value->IsArray()) {
88  return get_array_length(isolate, v8::Local<v8::Array>::Cast(array_value));
89  } else {
90  // TODO: probably throw?
91  throw V8AssertionException(isolate, "non-array passed to v8toolkit::get_array_length");
92  }
93 }
94 
95 
96 void set_global_object_alias(v8::Isolate * isolate, const v8::Local<v8::Context> context, std::string alias_name)
97 {
98  auto global_object = context->Global();
99  (void)global_object->Set(context, v8::String::NewFromUtf8(isolate, alias_name.c_str()), global_object);
100 
101 }
102 
103 
105 
106  // object, function, undefined, string, number, boolean, symbol
107  if (value->IsUndefined()) {
108  return "undefined";
109  } else if (value->IsFunction()) {
110  return "function";
111  } else if (value->IsString()) {
112  return "string";
113  } else if (value->IsNumber()) {
114  return "number";
115  } else if (value->IsBoolean()) {
116  return "boolean";
117  } else if (value->IsSymbol()) {
118  return "symbol";
119  } else if (value->IsObject()) {
120  return "object";
121  } else {
122  assert(false);
123  }
124  return "UNKNOWN VALUE TYPE";
125 }
126 
127 
128 
130 
131  auto value = *local_value;
132 
133  std::cout << "undefined: " << value->IsUndefined() << std::endl;
134  std::cout << "null: " << value->IsNull() << std::endl;
135  std::cout << "true: " << value->IsTrue() << std::endl;
136  std::cout << "false: " << value->IsFalse() << std::endl;
137  std::cout << "name: " << value->IsName() << std::endl;
138  std::cout << "string: " << value->IsString() << std::endl;
139  std::cout << "symbol: " << value->IsSymbol() << std::endl;
140  std::cout << "function: " << value->IsFunction() << std::endl;
141  std::cout << "array: " << value->IsArray() << std::endl;
142  std::cout << "object: " << value->IsObject() << std::endl;
143  std::cout << "boolean: " << value->IsBoolean() << std::endl;
144  std::cout << "number: " << value->IsNumber() << std::endl;
145  std::cout << "external: " << value->IsExternal() << std::endl;
146  std::cout << "isint32: " << value->IsInt32() << std::endl;
147  std::cout << "isuint32: " << value->IsUint32() << std::endl;
148  std::cout << "date: " << value->IsDate() << std::endl;
149  std::cout << "argument object: " << value->IsArgumentsObject() << std::endl;
150  std::cout << "boolean object: " << value->IsBooleanObject() << std::endl;
151  std::cout << "number object: " << value->IsNumberObject() << std::endl;
152  std::cout << "string object: " << value->IsStringObject() << std::endl;
153  std::cout << "symbol object: " << value->IsSymbolObject() << std::endl;
154  std::cout << "native error: " << value->IsNativeError() << std::endl;
155  std::cout << "regexp: " << value->IsRegExp() << std::endl;
156  std::cout << "generator function: " << value->IsGeneratorFunction() << std::endl;
157  std::cout << "generator object: " << value->IsGeneratorObject() << std::endl;
158 }
159 
160 std::set<std::string> make_set_from_object_keys(v8::Isolate * isolate,
161  v8::Local<v8::Object> & object,
162  bool own_properties_only)
163 {
164  auto context = isolate->GetCurrentContext();
165  v8::Local<v8::Array> properties;
166  if (own_properties_only) {
167  properties = object->GetOwnPropertyNames(context).ToLocalChecked();
168  } else {
169  properties = object->GetPropertyNames(context).ToLocalChecked();
170  }
171  auto array_length = get_array_length(isolate, properties);
172 
173  std::set<std::string> keys;
174 
175  for (int i = 0; i < array_length; i++) {
176  keys.insert(*v8::String::Utf8Value(properties->Get(context, i).ToLocalChecked()));
177  }
178 
179  return keys;
180 }
181 
182 
183 
184 
185 #define STRINGIFY_VALUE_DEBUG false
186 
187 std::string stringify_value(v8::Isolate * isolate,
188  const v8::Local<v8::Value> & value,
189  bool show_all_properties,
190  std::vector<v8::Local<v8::Value>> && processed_values)
191 {
192 
193  if (value.IsEmpty()) {
194  return "<Empty v8::Local<v8::Value>>";
195  }
196 
197 
198  auto context = isolate->GetCurrentContext();
199 
200  std::stringstream output;
201 
202  // Only protect against cycles on container types - otherwise a numeric value with
203  // the same number won't get shown twice
204  if (value->IsObject() || value->IsArray()) {
205  for(auto processed_value : processed_values) {
206  if(processed_value == value) {
208  if (STRINGIFY_VALUE_DEBUG) printf("Skipping previously processed value\n");
209  return "";
210  }
211  }
212  processed_values.push_back(value);
213  }
214 
215 
216  if(value.IsEmpty()) {
217  if (STRINGIFY_VALUE_DEBUG) printf("Value IsEmpty\n");
218  return "Value specified as an empty v8::Local";
219  }
220 
221  // if the left is a bool, return true if right is a bool and they match, otherwise false
222  if (value->IsBoolean() || value->IsNumber() || value->IsUndefined() || value->IsNull()) {
223  if (STRINGIFY_VALUE_DEBUG) printf("Stringify: treating value as 'normal'\n");
224  v8::String::Utf8Value value_utf8value(value);
225  auto string = *value_utf8value;
226  output << (string ? string : "<COULD NOT CONVERT TO STRING>");
227  //output << "<something broken here>";
228  } else if (value->IsFunction()) {
229  v8::String::Utf8Value value_utf8value(value);
230  auto string = *value_utf8value;
231  output << (string ? string : "FUNCTION: <COULD NOT CONVERT TO STRING>");
232 //
233 // output << "FUNCTION BUT PRINTING THEM IS WEIRD";
234 // if (value->IsObject()) {
235 // output << " - and is object too";
236 // }
237  } else if (value->IsString()) {
238  output << "\"" << *v8::String::Utf8Value(value) << "\"";
239  } else if (value->IsArray()) {
240  // printf("Stringify: treating value as array\n");
241  auto array = v8::Local<v8::Array>::Cast(value);
242  auto array_length = get_array_length(isolate, array);
243 
244  output << "[";
245  auto first_element = true;
246  for (int i = 0; i < array_length; i++) {
247  if (!first_element) {
248  output << ", ";
249  }
250  first_element = false;
251  auto value = array->Get(context, i);
252  output << stringify_value(isolate, value.ToLocalChecked(), show_all_properties, std::move(processed_values));
253  }
254  output << "]";
255  } else {
256  // printf("Stringify: treating value as object\n");
257  // check this last in case it's some other type of more specialized object we will test the specialization instead (like an array)
258  // objects must have all the same keys and each key must have the same as determined by calling this function on each value
259  // printf("About to see if we can stringify this as an object\n");
260  // print_v8_value_details(value);
261  auto object = v8::Local<v8::Object>::Cast(value);
262  output << "Object type: " << *v8::String::Utf8Value(object->GetConstructorName()) << std::endl;
263  if (object->InternalFieldCount() > 0) {
264  output << "Internal field pointer: " << (void *)v8::External::Cast(*object->GetInternalField(0))->Value() << std::endl;
265  }
266  if(value->IsObject() && !object.IsEmpty()) {
267  if (STRINGIFY_VALUE_DEBUG) printf("Stringifying object\n");
268  output << "{";
269  auto keys = make_set_from_object_keys(isolate, object, !show_all_properties);
270  auto first_key = true;
271  for(auto key : keys) {
272  if (STRINGIFY_VALUE_DEBUG) printf("Stringify: object key %s\n", key.c_str());
273  if (!first_key) {
274  output << ", ";
275  }
276  first_key = false;
277  output << key;
278  output << ": ";
279  auto value = object->Get(context, v8::String::NewFromUtf8(isolate, key.c_str()));
280  output << stringify_value(isolate, value.ToLocalChecked(), show_all_properties, std::move(processed_values));
281  }
282  output << "}";
283  }
284  }
285  return output.str();
286 }
287 
288 
290  return get_key_as<v8::Value>(context, object, key);
291 }
292 
294  return get_key_as<v8::Value>(context, get_value_as<v8::Object>(value), key);
295 }
296 
297 
298 
300  v8::Local<v8::Function> function) {
301 
302  v8::Local<v8::Context> context(isolate->GetCurrentContext());
303  v8::TryCatch tc(isolate);
304 
305  auto maybe_result = function->Call(context, context->Global(), 0, nullptr);
306  if(tc.HasCaught() || maybe_result.IsEmpty()) {
307  ReportException(isolate, &tc);
308  printf("Error running javascript function: '%s'\n", *v8::String::Utf8Value(tc.Exception()));
309  throw InvalidCallException(*v8::String::Utf8Value(tc.Exception()));
310  }
311  return maybe_result.ToLocalChecked();
312 }
313 
314 
315 // copied from shell_cc example
316 void ReportException(v8::Isolate* isolate, v8::TryCatch* try_catch) {
317  v8::HandleScope handle_scope(isolate);
318  v8::String::Utf8Value exception(try_catch->Exception());
319  const char* exception_string = *exception;
320  v8::Local<v8::Message> message = try_catch->Message();
321  if (message.IsEmpty()) {
322  // V8 didn't provide any extra information about this error; just
323  // print the exception.
324  fprintf(stderr, "%s\n", exception_string);
325  } else {
326  // Print (filename):(line number): (message).
327  v8::String::Utf8Value filename(message->GetScriptOrigin().ResourceName());
328  v8::Local<v8::Context> context(isolate->GetCurrentContext());
329  const char* filename_string = *filename;
330  int linenum = message->GetLineNumber(context).FromJust();
331  fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string);
332  // Print line of source code.
333  v8::String::Utf8Value sourceline(
334  message->GetSourceLine(context).ToLocalChecked());
335  const char* sourceline_string = *sourceline;
336  fprintf(stderr, "%s\n", sourceline_string);
337  // Print wavy underline (GetUnderline is deprecated).
338  int start = message->GetStartColumn(context).FromJust();
339  for (int i = 0; i < start; i++) {
340  fprintf(stderr, " ");
341  }
342  int end = message->GetEndColumn(context).FromJust();
343  for (int i = start; i < end; i++) {
344  fprintf(stderr, "^");
345  }
346  fprintf(stderr, "\n");
347  v8::Local<v8::Value> stack_trace_string;
348  if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) &&
349  stack_trace_string->IsString() &&
350  v8::Local<v8::String>::Cast(stack_trace_string)->Length() > 0) {
351  v8::String::Utf8Value stack_trace(stack_trace_string);
352  const char* stack_trace_string = *stack_trace;
353  fprintf(stderr, "%s\n", stack_trace_string);
354  }
355  }
356 }
357 
358 bool global_name_conflicts(const std::string & name) {
359  // for some reason the real code here is crashing
360  return false;
361 // if (std::find(reserved_global_names.begin(), reserved_global_names.end(), name) !=
362 // reserved_global_names.end()) {
363 // std::cerr << fmt::format("{} is a reserved js global name", name) << std::endl;
364 // return true;
365 // }
366 }
367 
368 std::vector<std::string> reserved_global_names = {"Boolean", "Null", "Undefined", "Number", "String",
369  "Object", "Symbol", "Date", "Array", "Set", "WeakSet",
370  "Map", "WeakMap", "JSON"};
371 
372 
373 
374 
375 
376 }
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
bool global_name_conflicts(const std::string &name)
Definition: v8helpers.cpp:358
std::string get_stack_trace_string(v8::Local< v8::StackTrace > stack_trace)
Definition: v8helpers.cpp:28
#define STRINGIFY_VALUE_DEBUG
Definition: v8helpers.cpp:185
::std::string string
Definition: gtest-port.h:1097
int get_array_length(v8::Isolate *isolate, v8::Local< v8::Value > array_value)
Definition: v8helpers.cpp:85
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
bool AnybaseDebugPrintFlag
Definition: v8helpers.cpp:11
v8::Local< v8::Value > get_key(v8::Local< v8::Context > context, v8::Local< v8::Object > object, std::string key)
Definition: v8helpers.cpp:289
bool Value(const T &value, M matcher)
void ReportException(v8::Isolate *isolate, v8::TryCatch *try_catch)
Definition: v8helpers.cpp:316
void print_v8_value_details(v8::Local< v8::Value > local_value)
Definition: v8helpers.cpp:129
std::vector< std::string > reserved_global_names
Definition: v8helpers.cpp:368
v8::Local< v8::Value > call_simple_javascript_function(v8::Isolate *isolate, v8::Local< v8::Function > function)
Definition: v8helpers.cpp:299
const T & move(const T &t)
Definition: gtest-port.h:1317
std::string demangle_typeid_name(const std::string &mangled_name)
Definition: v8helpers.cpp:45
void set_global_object_alias(v8::Isolate *isolate, const v8::Local< v8::Context > context, std::string alias_name)
Definition: v8helpers.cpp:96
std::string get_type_string_for_value(v8::Local< v8::Value > value)
Definition: v8helpers.cpp:104