#include #ifndef _WIN32 #include #endif #include #include #include #include #include using namespace v8; #define LOG_EXPR(e) std::cout << #e << " = " << (e) << std::endl #define LOG_VALUE_KIND(v) \ do { \ LOG_EXPR(v->IsUndefined()); \ LOG_EXPR(v->IsNull()); \ LOG_EXPR(v->IsNullOrUndefined()); \ LOG_EXPR(v->IsTrue()); \ LOG_EXPR(v->IsFalse()); \ LOG_EXPR(v->IsBoolean()); \ LOG_EXPR(v->IsString()); \ LOG_EXPR(v->IsObject()); \ LOG_EXPR(v->IsNumber()); \ } while (0) namespace v8tests { static void run_gc(const FunctionCallbackInfo &info) { auto *isolate = info.GetIsolate(); auto context = isolate->GetCurrentContext(); (void)info[0].As()->Call(context, Null(isolate), 0, nullptr); } static void log_buffer(const char *buf, int len) { for (int i = 0; i < len; i++) { printf("buf[%d] = 0x%02x\n", i, buf[i]); } } static std::string describe(Isolate *isolate, Local value) { if (value->IsUndefined()) { return "undefined"; } else if (value->IsNull()) { return "null"; } else if (value->IsTrue()) { return "true"; } else if (value->IsFalse()) { return "false"; } else if (value->IsString()) { char buf[1024] = {0}; value.As()->WriteUtf8(isolate, buf, sizeof(buf) - 1); std::string result = "\""; result += buf; result += "\""; return result; } else if (value->IsFunction()) { char buf[1024] = {0}; value.As()->GetName().As()->WriteUtf8(isolate, buf, sizeof(buf) - 1); std::string result = "function "; result += buf; result += "()"; return result; } else if (value->IsObject()) { return "[object Object]"; } else if (value->IsNumber()) { return std::to_string(value.As()->Value()); } else { return "unknown"; } } void fail(const FunctionCallbackInfo &info, const char *fmt, ...) { char buf[1024]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); Local message = String::NewFromUtf8(info.GetIsolate(), buf).ToLocalChecked(); info.GetReturnValue().Set(message); } void ok(const FunctionCallbackInfo &args) { args.GetReturnValue().Set(Undefined(args.GetIsolate())); } void test_v8_native_call(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local undefined = Undefined(isolate); info.GetReturnValue().Set(undefined); } void test_v8_primitives(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local v8_undefined = Undefined(isolate); LOG_VALUE_KIND(v8_undefined); Local v8_null = Null(isolate); LOG_VALUE_KIND(v8_null); Local v8_true = Boolean::New(isolate, true); LOG_VALUE_KIND(v8_true); Local v8_false = Boolean::New(isolate, false); LOG_VALUE_KIND(v8_false); return ok(info); } static void perform_number_test(const FunctionCallbackInfo &info, double number) { Isolate *isolate = info.GetIsolate(); Local v8_number = Number::New(isolate, number); LOG_EXPR(v8_number->Value()); LOG_VALUE_KIND(v8_number); return ok(info); } void test_v8_number_int(const FunctionCallbackInfo &info) { perform_number_test(info, 123.0); } void test_v8_number_large_int(const FunctionCallbackInfo &info) { // 2^33 perform_number_test(info, 8589934592.0); } void test_v8_number_fraction(const FunctionCallbackInfo &info) { perform_number_test(info, 2.5); } static void perform_string_test(const FunctionCallbackInfo &info, Local v8_string) { Isolate *isolate = info.GetIsolate(); char buf[256] = {0x7f}; int retval; int nchars; LOG_VALUE_KIND(v8_string); LOG_EXPR(v8_string->Length()); LOG_EXPR(v8_string->Utf8Length(isolate)); LOG_EXPR(v8_string->IsOneByte()); LOG_EXPR(v8_string->ContainsOnlyOneByte()); LOG_EXPR(v8_string->IsExternal()); LOG_EXPR(v8_string->IsExternalTwoByte()); LOG_EXPR(v8_string->IsExternalOneByte()); // check string has the right contents LOG_EXPR(retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, &nchars)); LOG_EXPR(nchars); log_buffer(buf, retval + 1); memset(buf, 0x7f, sizeof buf); // try with assuming the buffer is large enough LOG_EXPR(retval = v8_string->WriteUtf8(isolate, buf, -1, &nchars)); LOG_EXPR(nchars); log_buffer(buf, retval + 1); memset(buf, 0x7f, sizeof buf); // try with ignoring nchars (it should not try to store anything in a // nullptr) LOG_EXPR(retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, nullptr)); log_buffer(buf, retval + 1); memset(buf, 0x7f, sizeof buf); return ok(info); } template void perform_string_test_normal_and_internalized( const FunctionCallbackInfo &info, const T *string_literal, bool latin1 = false) { Isolate *isolate = info.GetIsolate(); if (latin1) { const uint8_t *string = reinterpret_cast(string_literal); perform_string_test( info, String::NewFromOneByte(isolate, string, NewStringType::kNormal) .ToLocalChecked()); perform_string_test(info, String::NewFromOneByte( isolate, string, NewStringType::kInternalized) .ToLocalChecked()); } else { const char *string = reinterpret_cast(string_literal); perform_string_test( info, String::NewFromUtf8(isolate, string, NewStringType::kNormal) .ToLocalChecked()); perform_string_test( info, String::NewFromUtf8(isolate, string, NewStringType::kInternalized) .ToLocalChecked()); } } void test_v8_string_ascii(const FunctionCallbackInfo &info) { perform_string_test_normal_and_internalized(info, "hello world"); } void test_v8_string_utf8(const FunctionCallbackInfo &info) { const unsigned char trans_flag_unsigned[] = {240, 159, 143, 179, 239, 184, 143, 226, 128, 141, 226, 154, 167, 239, 184, 143, 0}; perform_string_test_normal_and_internalized(info, trans_flag_unsigned); } void test_v8_string_invalid_utf8(const FunctionCallbackInfo &info) { const unsigned char mixed_sequence_unsigned[] = {'o', 'h', ' ', 0xc0, 'n', 'o', 0xc2, '!', 0xf5, 0}; perform_string_test_normal_and_internalized(info, mixed_sequence_unsigned); } void test_v8_string_latin1(const FunctionCallbackInfo &info) { const unsigned char latin1[] = {0xa1, 'b', 'u', 'n', '!', 0}; perform_string_test_normal_and_internalized(info, latin1, true); auto string = String::NewFromOneByte(info.GetIsolate(), latin1, NewStringType::kNormal, 1) .ToLocalChecked(); perform_string_test(info, string); } void test_v8_string_write_utf8(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); const unsigned char utf8_data_unsigned[] = { 'h', 'i', 240, 159, 143, 179, 239, 184, 143, 226, 128, 141, 226, 154, 167, 239, 184, 143, 'h', 'i', 0xc3, 0xa9, 0}; const char *utf8_data = reinterpret_cast(utf8_data_unsigned); constexpr int buf_size = sizeof(utf8_data_unsigned) + 3; char buf[buf_size] = {0}; Local s = String::NewFromUtf8(isolate, utf8_data).ToLocalChecked(); for (int i = buf_size; i >= 0; i--) { memset(buf, 0xaa, buf_size); int nchars; int retval = s->WriteUtf8(isolate, buf, i, &nchars); printf("buffer size = %2d, nchars = %2d, returned = %2d, data =", i, nchars, retval); for (int j = 0; j < buf_size; j++) { printf("%c%02x", j == i ? '|' : ' ', reinterpret_cast(buf)[j]); } printf("\n"); } return ok(info); } void test_v8_external(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); int x = 5; Local external = External::New(isolate, &x); LOG_EXPR(*reinterpret_cast(external->Value())); if (external->Value() != &x) { return fail(info, "External::Value() returned wrong pointer: expected %p got %p", &x, external->Value()); } return ok(info); } void test_v8_object(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local context = isolate->GetCurrentContext(); Local obj = Object::New(isolate); auto key = String::NewFromUtf8(isolate, "key").ToLocalChecked(); auto val = Number::New(isolate, 5.0); Maybe set_status = obj->Set(context, key, val); LOG_EXPR(set_status.IsJust()); LOG_EXPR(set_status.FromJust()); // Local retval = obj->Get(context, key).ToLocalChecked(); // LOG_EXPR(describe(isolate, retval)); return ok(info); } void test_v8_array_new(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local vals[5] = { Number::New(isolate, 50.0), String::NewFromUtf8(isolate, "meow").ToLocalChecked(), Number::New(isolate, 8.5), Null(isolate), Boolean::New(isolate, true), }; Local v8_array = Array::New(isolate, vals, sizeof(vals) / sizeof(Local)); LOG_EXPR(v8_array->Length()); for (uint32_t i = 0; i < 5; i++) { Local array_value = v8_array->Get(isolate->GetCurrentContext(), i).ToLocalChecked(); if (!array_value->StrictEquals(vals[i])) { printf("array[%u] does not match\n", i); } LOG_EXPR(describe(isolate, array_value)); } return ok(info); } void test_v8_object_template(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local context = isolate->GetCurrentContext(); Local obj_template = ObjectTemplate::New(isolate); obj_template->SetInternalFieldCount(2); LOG_EXPR(obj_template->InternalFieldCount()); Local obj1 = obj_template->NewInstance(context).ToLocalChecked(); obj1->SetInternalField(0, Number::New(isolate, 3.0)); obj1->SetInternalField(1, Number::New(isolate, 4.0)); Local obj2 = obj_template->NewInstance(context).ToLocalChecked(); obj2->SetInternalField(0, Number::New(isolate, 5.0)); obj2->SetInternalField(1, Number::New(isolate, 6.0)); LOG_EXPR(obj1->GetInternalField(0).As()->Value()); LOG_EXPR(obj1->GetInternalField(1).As()->Value()); LOG_EXPR(obj2->GetInternalField(0).As()->Value()); LOG_EXPR(obj2->GetInternalField(1).As()->Value()); } void return_data_callback(const FunctionCallbackInfo &info) { info.GetReturnValue().Set(info.Data()); } void create_function_with_data(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local context = isolate->GetCurrentContext(); Local s = String::NewFromUtf8(isolate, "hello world").ToLocalChecked(); Local tmp = FunctionTemplate::New(isolate, return_data_callback, s); Local f = tmp->GetFunction(context).ToLocalChecked(); Local name = String::NewFromUtf8(isolate, "function_with_data").ToLocalChecked(); f->SetName(name); info.GetReturnValue().Set(f); } void print_values_from_js(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); printf("%d arguments\n", info.Length()); printf("this = %s\n", describe(isolate, info.This()).c_str()); for (int i = 0; i < info.Length(); i++) { printf("argument %d = %s\n", i, describe(isolate, info[i]).c_str()); } return ok(info); } void return_this(const FunctionCallbackInfo &info) { info.GetReturnValue().Set(info.This()); } class GlobalTestWrapper { public: static void set(const FunctionCallbackInfo &info); static void get(const FunctionCallbackInfo &info); static void cleanup(void *unused); private: static Global value; }; Global GlobalTestWrapper::value; void GlobalTestWrapper::set(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); if (value.IsEmpty()) { info.GetReturnValue().Set(Undefined(isolate)); } else { info.GetReturnValue().Set(value.Get(isolate)); } const auto new_value = info[0]; value.Reset(isolate, new_value); } void GlobalTestWrapper::get(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); if (value.IsEmpty()) { info.GetReturnValue().Set(Undefined(isolate)); } else { info.GetReturnValue().Set(value.Get(isolate)); } } void GlobalTestWrapper::cleanup(void *unused) { value.Reset(); } void test_many_v8_locals(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local nums[1000]; for (int i = 0; i < 1000; i++) { nums[i] = Number::New(isolate, (double)i + 0.5); } // try accessing them all to make sure the pointers are stable for (int i = 0; i < 1000; i++) { LOG_EXPR(nums[i]->Value()); } } void print_cell_location(Local v8_value, const char *fmt, ...) { (void)v8_value; (void)fmt; // va_list ap; // va_start(ap, fmt); // vprintf(fmt, ap); // va_end(ap); // uintptr_t *slot = *reinterpret_cast(&v8_value); // uintptr_t tagged = *slot; // uintptr_t addr = tagged & ~3; // struct ObjectLayout { // uintptr_t map; // void *cell; // }; // void *cell = reinterpret_cast(addr)->cell; // printf(" = %p\n", cell); } static Local setup_object_with_string_field(Isolate *isolate, Local context, Local tmp, int i, const std::string &str) { EscapableHandleScope ehs(isolate); Local o = tmp->NewInstance(context).ToLocalChecked(); print_cell_location(o, "objects[%3d] ", i); Local value = String::NewFromUtf8(isolate, str.c_str()).ToLocalChecked(); print_cell_location(value, "objects[%3d]->0", i); o->SetInternalField(0, value); return ehs.Escape(o); } static void examine_object_fields(Isolate *isolate, Local o, int expected_field0, int expected_field1) { char buf[16]; HandleScope hs(isolate); o->GetInternalField(0).As()->WriteUtf8(isolate, buf); assert(atoi(buf) == expected_field0); Local field1 = o->GetInternalField(1).As(); if (field1->IsString()) { field1.As()->WriteUtf8(isolate, buf); assert(atoi(buf) == expected_field1); } else { assert(field1->IsUndefined()); } } void test_handle_scope_gc(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local context = isolate->GetCurrentContext(); // allocate a ton of objects constexpr size_t num_small_allocs = 500; Local mini_strings[num_small_allocs]; for (size_t i = 0; i < num_small_allocs; i++) { std::string cpp_str = std::to_string(i); mini_strings[i] = String::NewFromUtf8(isolate, cpp_str.c_str()).ToLocalChecked(); print_cell_location(mini_strings[i], "mini_strings[%3d]", i); } // allocate some objects with internal fields, to check that those are // traced Local tmp = ObjectTemplate::New(isolate); tmp->SetInternalFieldCount(2); print_cell_location(tmp, "object template"); print_cell_location(context, "context"); Local objects[num_small_allocs]; for (size_t i = 0; i < num_small_allocs; i++) { std::string cpp_str = std::to_string(i + num_small_allocs); // this uses a function so that the strings aren't kept alive by the // current handle scope objects[i] = setup_object_with_string_field(isolate, context, tmp, i, cpp_str); } // allocate some massive strings // this should cause GC to start looking for objects to free // after each big string allocation, we try reading all of the strings we // created above to ensure they are still alive constexpr size_t num_strings = 50; constexpr size_t string_size = 20 * 1000 * 1000; auto string_data = new char[string_size]; string_data[string_size - 1] = 0; Local huge_strings[num_strings]; for (size_t i = 0; i < num_strings; i++) { printf("%zu\n", i); memset(string_data, i + 1, string_size - 1); huge_strings[i] = String::NewFromUtf8(isolate, string_data).ToLocalChecked(); // try to use all mini strings for (size_t j = 0; j < num_small_allocs; j++) { char buf[16]; mini_strings[j]->WriteUtf8(isolate, buf); assert(atoi(buf) == (int)j); } for (size_t j = 0; j < num_small_allocs; j++) { examine_object_fields(isolate, objects[j], j + num_small_allocs, j + 2 * num_small_allocs); } if (i == 1) { // add more internal fields to the objects a long time after they were // created, to ensure these can also be traced // make a new handlescope here so that the new strings we allocate are // only referenced by the objects HandleScope inner_hs(isolate); for (auto &o : objects) { int i = &o - &objects[0]; auto cpp_str = std::to_string(i + 2 * num_small_allocs); Local field = String::NewFromUtf8(isolate, cpp_str.c_str()).ToLocalChecked(); o->SetInternalField(1, field); } } } memset(string_data, 0, string_size); for (size_t i = 0; i < num_strings; i++) { huge_strings[i]->WriteUtf8(isolate, string_data); for (size_t j = 0; j < string_size - 1; j++) { assert(string_data[j] == (char)(i + 1)); } } delete[] string_data; } Local escape_object(Isolate *isolate) { EscapableHandleScope ehs(isolate); Local invalidated = String::NewFromUtf8(isolate, "hello").ToLocalChecked(); Local escaped = ehs.Escape(invalidated); return escaped; } Local escape_smi(Isolate *isolate) { EscapableHandleScope ehs(isolate); Local invalidated = Number::New(isolate, 3.0); Local escaped = ehs.Escape(invalidated); return escaped; } Local escape_true(Isolate *isolate) { EscapableHandleScope ehs(isolate); Local invalidated = v8::True(isolate); Local escaped = ehs.Escape(invalidated); return escaped; } void test_v8_escapable_handle_scope(const FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); Local s = escape_object(isolate); Local n = escape_smi(isolate); Local t = escape_true(isolate); LOG_VALUE_KIND(s); LOG_VALUE_KIND(n); LOG_VALUE_KIND(t); char buf[16]; s->WriteUtf8(isolate, buf); LOG_EXPR(buf); LOG_EXPR(n->Value()); } void test_uv_os_getpid(const FunctionCallbackInfo &info) { #ifndef _WIN32 assert(getpid() == uv_os_getpid()); #else assert(0 && "unreachable"); #endif return ok(info); } void test_uv_os_getppid(const FunctionCallbackInfo &info) { #ifndef _WIN32 assert(getppid() == uv_os_getppid()); #else assert(0 && "unreachable"); #endif return ok(info); } void initialize(Local exports, Local module, Local context) { NODE_SET_METHOD(exports, "test_v8_native_call", test_v8_native_call); NODE_SET_METHOD(exports, "test_v8_primitives", test_v8_primitives); NODE_SET_METHOD(exports, "test_v8_number_int", test_v8_number_int); NODE_SET_METHOD(exports, "test_v8_number_large_int", test_v8_number_large_int); NODE_SET_METHOD(exports, "test_v8_number_fraction", test_v8_number_fraction); NODE_SET_METHOD(exports, "test_v8_string_ascii", test_v8_string_ascii); NODE_SET_METHOD(exports, "test_v8_string_utf8", test_v8_string_utf8); NODE_SET_METHOD(exports, "test_v8_string_invalid_utf8", test_v8_string_invalid_utf8); NODE_SET_METHOD(exports, "test_v8_string_latin1", test_v8_string_latin1); NODE_SET_METHOD(exports, "test_v8_string_write_utf8", test_v8_string_write_utf8); NODE_SET_METHOD(exports, "test_v8_external", test_v8_external); NODE_SET_METHOD(exports, "test_v8_object", test_v8_object); NODE_SET_METHOD(exports, "test_v8_array_new", test_v8_array_new); NODE_SET_METHOD(exports, "test_v8_object_template", test_v8_object_template); NODE_SET_METHOD(exports, "create_function_with_data", create_function_with_data); NODE_SET_METHOD(exports, "print_values_from_js", print_values_from_js); NODE_SET_METHOD(exports, "return_this", return_this); NODE_SET_METHOD(exports, "global_get", GlobalTestWrapper::get); NODE_SET_METHOD(exports, "global_set", GlobalTestWrapper::set); NODE_SET_METHOD(exports, "test_many_v8_locals", test_many_v8_locals); NODE_SET_METHOD(exports, "test_handle_scope_gc", test_handle_scope_gc); NODE_SET_METHOD(exports, "test_v8_escapable_handle_scope", test_v8_escapable_handle_scope); NODE_SET_METHOD(exports, "test_uv_os_getpid", test_uv_os_getpid); NODE_SET_METHOD(exports, "test_uv_os_getppid", test_uv_os_getppid); // without this, node hits a UAF deleting the Global node::AddEnvironmentCleanupHook(context->GetIsolate(), GlobalTestWrapper::cleanup, nullptr); } NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, initialize) } // namespace v8tests