1 /* 2 * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 */ 4 5 #include <sys/types.h> 6 #include <stdarg.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <alloca.h> 10 #include <dlfcn.h> 11 #include <libnvpair.h> 12 #include <node.h> 13 #include <v8.h> 14 #include <unordered_map> 15 #include <string> 16 #include "v8plus_impl.h" 17 18 #define V8PLUS_OBJ_TYPE_MEMBER ".__v8plus_type" 19 #define V8_EXCEPTION_CTOR_FMT \ 20 "_ZN2v89Exception%u%sENS_6HandleINS_6StringEEE" 21 22 typedef struct cb_hdl { 23 v8::Handle<v8::Function> ch_hdl; 24 uint_t ch_refs; 25 boolean_t ch_persist; 26 } cb_hdl_t; 27 28 static std::unordered_map<uint64_t, cb_hdl_t> cbhash; 29 static uint64_t cbnext; 30 static void (*__real_nvlist_free)(nvlist_t *); 31 32 static const char * 33 cstr(const v8::String::Utf8Value &v) 34 { 35 return (*v); 36 } 37 38 /* 39 * Convenience macros for adding stuff to an nvlist and returning on failure. 40 */ 41 #define LA_U(_l, _n, _e) \ 42 if (((_e) = nvlist_add_boolean((_l), (_n))) != 0) \ 43 return (_e) 44 45 #define LA_N(_l, _n, _e) \ 46 if (((_e) = nvlist_add_byte((_l), (_n), 0)) != 0) \ 47 return (_e) 48 49 #define LA_V(_l, _t, _n, _v, _e) \ 50 if (((_e) = nvlist_add_##_t((_l), (_n), (_v))) != 0) \ 51 return (_e) 52 53 #define LA_VA(_l, _t, _n, _v, _c, _e) \ 54 if (((_e) = nvlist_add_##_t##_array((_l), (_n), (_v), (_c))) != 0) \ 55 return (_e) 56 57 /* 58 * Add an element named <name> to list <lp> with a transcoded value 59 * corresponding to <vh> if possible. Only primitive types, objects that are 60 * thin wrappers for primitive types, and objects containing members whose 61 * types are all any of the above can be transcoded. 62 * 63 * Booleans and their Object type are encoded as boolean_value. 64 * Numbers and their Object type are encoded as double. 65 * Strings and their Object type are encoded as C strings (and assumed UTF-8). 66 * Any Object (including an Array) is encoded as an nvlist whose elements 67 * are the Object's own properties. 68 * Null is encoded as a byte with value 0. 69 * Undefined is encoded as the valueless boolean. 70 * 71 * Returns EINVAL if any argument fails these tests, or any other error code 72 * that may be returned by nvlist_add_XXX(3nvpair). 73 */ 74 static int 75 nvlist_add_v8_Value(nvlist_t *lp, const char *name, 76 const v8::Handle<v8::Value> &vh) 77 { 78 int err = 0; 79 80 if (vh->IsBoolean()) { 81 boolean_t vv = vh->BooleanValue() ? _B_TRUE : _B_FALSE; 82 LA_V(lp, boolean_value, name, vv, err); 83 } else if (vh->IsNumber()) { 84 double vv = vh->NumberValue(); 85 LA_V(lp, double, name, vv, err); 86 } else if (vh->IsString()) { 87 v8::String::Utf8Value s(vh); 88 const char *vv = cstr(s); 89 LA_V(lp, string, name, vv, err); 90 } else if (vh->IsUndefined()) { 91 LA_U(lp, name, err); 92 } else if (vh->IsNull()) { 93 LA_N(lp, name, err); 94 } else if (vh->IsNumberObject()) { 95 double vv = vh->NumberValue(); 96 LA_V(lp, double, name, vv, err); 97 } else if (vh->IsStringObject()) { 98 v8::String::Utf8Value s(vh); 99 const char *vv = cstr(s); 100 LA_V(lp, string, name, vv, err); 101 } else if (vh->IsBooleanObject()) { 102 boolean_t vv = vh->BooleanValue() ? _B_TRUE : _B_FALSE; 103 LA_V(lp, boolean_value, name, vv, err); 104 } else if (vh->IsFunction()) { 105 cb_hdl_t ch; 106 107 ch.ch_hdl = v8::Handle<v8::Function>::Cast(vh); 108 ch.ch_refs = 1; 109 ch.ch_persist = _B_FALSE; 110 111 while (cbhash.find(cbnext) != cbhash.end()) 112 ++cbnext; 113 cbhash.insert(std::make_pair(cbnext, ch)); 114 115 LA_VA(lp, string, V8PLUS_JSF_COOKIE, NULL, 0, err); 116 LA_VA(lp, uint64, name, &cbnext, 1, err); 117 } else if (vh->IsObject()) { 118 v8::Local<v8::Object> oh = vh->ToObject(); 119 v8::Local<v8::Array> keys = oh->GetOwnPropertyNames(); 120 v8::Local<v8::String> th = oh->GetConstructorName(); 121 v8::String::Utf8Value tv(th); 122 const char *type = cstr(tv); 123 nvlist_t *vlp; 124 uint_t i; 125 126 if ((err = nvlist_alloc(&vlp, NV_UNIQUE_NAME, 0)) != 0) 127 return (err); 128 129 /* XXX this is vile; can we handle this generally? */ 130 if (strcmp(type, "Object") != 0) { 131 if (strcmp(type, "Array") == 0) { 132 if ((err = nvlist_add_string(vlp, 133 V8PLUS_OBJ_TYPE_MEMBER, type)) != 0) { 134 nvlist_free(vlp); 135 return (err); 136 } 137 } else { 138 /* 139 * XXX This is (C) programmer error. Should 140 * we plumb up a way to throw here? 141 */ 142 (void) v8plus_panic("can't handle %s", type); 143 } 144 } 145 146 for (i = 0; i < keys->Length(); i++) { 147 char knname[16]; 148 v8::Local<v8::Value> mk; 149 v8::Local<v8::Value> mv; 150 const char *k; 151 152 (void) snprintf(knname, sizeof (knname), "%u", i); 153 mk = keys->Get(v8::String::New(knname)); 154 mv = oh->Get(mk); 155 v8::String::Utf8Value mks(mk); 156 k = cstr(mks); 157 158 if ((err = nvlist_add_v8_Value(vlp, k, mv)) != 0) { 159 nvlist_free(vlp); 160 return (err); 161 } 162 } 163 164 LA_V(lp, nvlist, name, vlp, err); 165 } else { 166 return (EINVAL); 167 } 168 169 return (0); 170 } 171 172 #undef LA_U 173 #undef LA_N 174 #undef LA_V 175 176 nvlist_t * 177 v8plus::v8_Arguments_to_nvlist(const v8::Arguments &args) 178 { 179 char name[16]; 180 nvlist_t *lp; 181 int err; 182 uint_t i; 183 184 if ((err = nvlist_alloc(&lp, NV_UNIQUE_NAME, 0)) != 0) 185 return (v8plus_nverr(err, NULL)); 186 187 for (i = 0; i < (uint_t)args.Length(); i++) { 188 (void) snprintf(name, sizeof (name), "%u", i); 189 if ((err = nvlist_add_v8_Value(lp, name, args[i])) != 0) { 190 nvlist_free(lp); 191 return (v8plus_nverr(err, name)); 192 } 193 } 194 195 return (lp); 196 } 197 198 static void 199 decorate_object(v8::Local<v8::Object> &oh, const nvlist_t *lp) 200 { 201 nvpair_t *pp = NULL; 202 203 while ((pp = 204 nvlist_next_nvpair(const_cast<nvlist_t *>(lp), pp)) != NULL) { 205 oh->Set(v8::String::New(nvpair_name(pp)), 206 v8plus::nvpair_to_v8_Value(pp)); 207 } 208 } 209 210 #define RETURN_JS(_p, _jt, _ct, _xt, _pt) \ 211 do { \ 212 _ct _v; \ 213 (void) nvpair_value_##_pt(const_cast<nvpair_t *>(_p), &_v); \ 214 return (v8::_jt::New((_xt)_v)); \ 215 } while (0) 216 217 v8::Handle<v8::Value> 218 v8plus::nvpair_to_v8_Value(const nvpair_t *pp) 219 { 220 const char *type; 221 222 switch (nvpair_type(const_cast<nvpair_t *>(pp))) { 223 case DATA_TYPE_BOOLEAN: 224 return (v8::Undefined()); 225 case DATA_TYPE_BOOLEAN_VALUE: 226 RETURN_JS(pp, Boolean, boolean_t, bool, boolean_value); 227 case DATA_TYPE_BYTE: 228 { 229 uint8_t _v = (uint8_t)-1; 230 231 if (nvpair_value_byte(const_cast<nvpair_t *>(pp), &_v) != 0 || 232 _v != 0) { 233 v8plus_panic("bad byte value %02x\n", _v); 234 } 235 236 return (v8::Null()); 237 } 238 case DATA_TYPE_INT8: 239 RETURN_JS(pp, Number, int8_t, double, int8); 240 case DATA_TYPE_UINT8: 241 RETURN_JS(pp, Number, uint8_t, double, uint8); 242 case DATA_TYPE_INT16: 243 RETURN_JS(pp, Number, int16_t, double, int16); 244 case DATA_TYPE_UINT16: 245 RETURN_JS(pp, Number, uint16_t, double, uint16); 246 case DATA_TYPE_INT32: 247 RETURN_JS(pp, Number, int32_t, double, int32); 248 case DATA_TYPE_UINT32: 249 RETURN_JS(pp, Number, uint32_t, double, uint32); 250 case DATA_TYPE_INT64: 251 RETURN_JS(pp, Number, int64_t, double, int64); 252 case DATA_TYPE_UINT64: 253 RETURN_JS(pp, Number, uint64_t, double, uint64); 254 case DATA_TYPE_DOUBLE: 255 RETURN_JS(pp, Number, double, double, double); 256 case DATA_TYPE_STRING: 257 RETURN_JS(pp, String, char *, const char *, string); 258 case DATA_TYPE_UINT64_ARRAY: 259 { 260 std::unordered_map<uint64_t, cb_hdl_t>::iterator it; 261 uint64_t *vp; 262 uint_t nv; 263 int err; 264 265 if ((err = nvpair_value_uint64_array(const_cast<nvpair_t *>(pp), 266 &vp, &nv)) != 0) 267 v8plus_panic("bad JSFUNC pair: %s", strerror(err)); 268 if (nv != 1) 269 v8plus_panic("bad uint64 array length %u", nv); 270 if ((it = cbhash.find(*vp)) == cbhash.end()) 271 v8plus_panic("callback hash tag %llu not found", *vp); 272 273 return (it->second.ch_hdl); 274 } 275 case DATA_TYPE_NVLIST: 276 { 277 nvlist_t *lp; 278 v8::Local<v8::Object> oh; 279 280 (void) nvpair_value_nvlist(const_cast<nvpair_t *>(pp), &lp); 281 282 if (nvlist_lookup_string(const_cast<nvlist_t *>(lp), 283 V8PLUS_OBJ_TYPE_MEMBER, const_cast<char **>(&type)) != 0) 284 type = "Object"; 285 286 if (strcmp(type, "Array") == 0) 287 oh = v8::Array::New()->ToObject(); 288 else if (strcmp(type, "Object") != 0) 289 v8plus_panic("bad object type %s\n", type); 290 else 291 oh = v8::Object::New(); 292 293 decorate_object(oh, lp); 294 return (oh); 295 } 296 default: 297 v8plus_panic("bad data type %d\n", 298 nvpair_type(const_cast<nvpair_t *>(pp))); 299 } 300 301 /*NOTREACHED*/ 302 return (v8::Undefined()); 303 } 304 305 #undef RETURN_JS 306 307 static uint_t 308 nvlist_length(const nvlist_t *lp) 309 { 310 uint_t l = 0; 311 nvpair_t *pp = NULL; 312 313 while ((pp = 314 nvlist_next_nvpair(const_cast<nvlist_t *>(lp), pp)) != NULL) 315 ++l; 316 317 return (l); 318 } 319 320 static void 321 nvlist_to_v8_argv(const nvlist_t *lp, int *argcp, v8::Handle<v8::Value> *argv) 322 { 323 nvpair_t *pp; 324 char name[16]; 325 int i; 326 327 for (i = 0; i < *argcp; i++) { 328 (void) snprintf(name, sizeof (name), "%u", i); 329 if (nvlist_lookup_nvpair(const_cast<nvlist_t *>(lp), 330 name, &pp) != 0) 331 break; 332 argv[i] = v8plus::nvpair_to_v8_Value(pp); 333 } 334 335 *argcp = i; 336 } 337 338 static v8::Local<v8::Value> 339 sexception(const char *type, const nvlist_t *lp, const char *msg) 340 { 341 char *ctor_name; 342 v8::Local<v8::Value> (*excp_ctor)(v8::Handle<v8::String>); 343 void *obj_hdl; 344 size_t len; 345 v8::Local<v8::Value> excp; 346 v8::Local<v8::Object> obj; 347 v8::Local<v8::String> jsmsg = v8::String::New(msg); 348 349 if (type == NULL) { 350 type = v8plus_excptype(_v8plus_errno); 351 if (type == NULL) 352 type = "Error"; 353 } 354 355 len = snprintf(NULL, 0, V8_EXCEPTION_CTOR_FMT, 356 (uint_t)strlen(type), type); 357 ctor_name = reinterpret_cast<char *>(alloca(len + 1)); 358 (void) snprintf(ctor_name, len + 1, V8_EXCEPTION_CTOR_FMT, 359 (uint_t)strlen(type), type); 360 361 obj_hdl = dlopen(NULL, RTLD_NOLOAD); 362 if (obj_hdl == NULL) 363 v8plus_panic("%s\n", dlerror()); 364 365 excp_ctor = (v8::Local<v8::Value>(*)(v8::Handle<v8::String>))( 366 dlsym(obj_hdl, ctor_name)); 367 368 if (excp_ctor == NULL) { 369 (void) dlclose(obj_hdl); 370 if (strcmp(type, "Error") == 0) { 371 v8plus_panic("Unable to find %s, aborting\n", 372 ctor_name); 373 } else { 374 excp = v8::Exception::Error(v8::String::New( 375 "Nested exception: illegal exception type")); 376 return (excp); 377 } 378 } 379 380 excp = excp_ctor(jsmsg); 381 (void) dlclose(obj_hdl); 382 383 if (lp == NULL) 384 return (excp); 385 386 obj = excp->ToObject(); 387 decorate_object(obj, lp); 388 389 return (excp); 390 } 391 392 v8::Local<v8::Value> 393 v8plus::exception(const char *type, const nvlist_t *lp, const char *fmt, ...) 394 { 395 v8::Local<v8::Value> exception; 396 char *msg; 397 size_t len; 398 va_list ap; 399 400 if (fmt != NULL) { 401 va_start(ap, fmt); 402 len = vsnprintf(NULL, 0, fmt, ap); 403 va_end(ap); 404 msg = reinterpret_cast<char *>(alloca(len + 1)); 405 406 va_start(ap, fmt); 407 (void) vsnprintf(msg, len + 1, fmt, ap); 408 va_end(ap); 409 } else { 410 msg = _v8plus_errmsg; 411 } 412 413 exception = sexception(type, lp, msg); 414 415 return (exception); 416 } 417 418 extern "C" nvlist_t * 419 v8plus_call_direct(v8plus_jsfunc_t f, const nvlist_t *lp) 420 { 421 std::unordered_map<uint64_t, cb_hdl_t>::iterator it; 422 const int max_argc = nvlist_length(lp); 423 int argc, err; 424 v8::Handle<v8::Value> argv[max_argc]; 425 v8::Handle<v8::Value> res; 426 nvlist_t *rp; 427 428 if ((it = cbhash.find(f)) == cbhash.end()) 429 v8plus_panic("callback hash tag %llu not found", f); 430 431 argc = max_argc; 432 nvlist_to_v8_argv(lp, &argc, argv); 433 434 if ((err = nvlist_alloc(&rp, NV_UNIQUE_NAME, 0)) != 0) 435 return (v8plus_nverr(err, NULL)); 436 437 v8::TryCatch tc; 438 res = it->second.ch_hdl->Call(v8::Context::GetCurrent()->Global(), 439 argc, argv); 440 if (tc.HasCaught()) { 441 err = nvlist_add_v8_Value(rp, "err", tc.Exception()); 442 tc.Reset(); 443 if (err != 0) { 444 nvlist_free(rp); 445 return (v8plus_nverr(err, "err")); 446 } 447 } else if ((err = nvlist_add_v8_Value(rp, "res", res)) != 0) { 448 nvlist_free(rp); 449 return (v8plus_nverr(err, "res")); 450 } 451 452 return (rp); 453 } 454 455 extern "C" nvlist_t * 456 v8plus_method_call_direct(void *cop, const char *name, const nvlist_t *lp) 457 { 458 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop); 459 const int max_argc = nvlist_length(lp); 460 int argc, err; 461 v8::Handle<v8::Value> argv[max_argc]; 462 v8::Handle<v8::Value> res; 463 nvlist_t *rp; 464 465 if (v8plus_in_event_thread() != _B_TRUE) 466 v8plus_panic("direct method call outside of event loop"); 467 468 argc = max_argc; 469 nvlist_to_v8_argv(lp, &argc, argv); 470 471 if ((err = nvlist_alloc(&rp, NV_UNIQUE_NAME, 0)) != 0) 472 return (v8plus_nverr(err, NULL)); 473 474 v8::TryCatch tc; 475 res = op->call(name, argc, argv); 476 if (tc.HasCaught()) { 477 err = nvlist_add_v8_Value(rp, "err", tc.Exception()); 478 tc.Reset(); 479 if (err != 0) { 480 nvlist_free(rp); 481 return (v8plus_nverr(err, "err")); 482 } 483 } else if ((err = nvlist_add_v8_Value(rp, "res", res)) != 0) { 484 nvlist_free(rp); 485 return (v8plus_nverr(err, "res")); 486 } 487 488 return (rp); 489 } 490 491 extern "C" int 492 nvlist_lookup_v8plus_jsfunc(const nvlist_t *lp, const char *name, 493 v8plus_jsfunc_t *vp) 494 { 495 uint64_t *lvp; 496 uint_t nv; 497 int err; 498 499 err = nvlist_lookup_uint64_array(const_cast<nvlist_t *>(lp), 500 name, &lvp, &nv); 501 if (err != 0) 502 return (err); 503 504 if (nv != 1) 505 v8plus_panic("bad array size %u for callback hash tag", nv); 506 507 *vp = *lvp; 508 return (0); 509 } 510 511 extern "C" void 512 v8plus_jsfunc_hold(v8plus_jsfunc_t f) 513 { 514 v8::Persistent<v8::Function> pfh; 515 std::unordered_map<uint64_t, cb_hdl_t>::iterator it; 516 517 if ((it = cbhash.find(f)) == cbhash.end()) 518 v8plus_panic("callback hash tag %llu not found", f); 519 520 if (!it->second.ch_persist) { 521 pfh = v8::Persistent<v8::Function>::New(it->second.ch_hdl); 522 it->second.ch_hdl = pfh; 523 it->second.ch_persist = _B_TRUE; 524 } 525 ++it->second.ch_refs; 526 527 /* 528 * If the consumer puts a hold on a callback, we should also put a hold 529 * on the V8 event loop to prevent it dematerialising beneath us. 530 */ 531 v8plus_eventloop_hold(); 532 } 533 534 extern "C" void 535 v8plus_jsfunc_rele_direct(v8plus_jsfunc_t f) 536 { 537 v8::Local<v8::Function> lfh; 538 std::unordered_map<uint64_t, cb_hdl_t>::iterator it; 539 540 if ((it = cbhash.find(f)) == cbhash.end()) 541 v8plus_panic("callback hash tag %llu not found", f); 542 543 if (it->second.ch_refs == 0) 544 v8plus_panic("releasing unheld callback hash tag %llu", f); 545 546 if (--it->second.ch_refs == 0) { 547 if (it->second.ch_persist) { 548 v8::Persistent<v8::Function> pfh(it->second.ch_hdl); 549 pfh.Dispose(); 550 } 551 cbhash.erase(it); 552 } 553 554 /* 555 * Release the event loop hold we took in v8plus_jsfunc_hold(): 556 */ 557 v8plus_eventloop_rele_direct(); 558 } 559 560 static size_t 561 library_name(const char *base, const char *version, char *buf, size_t len) 562 { 563 #ifdef __MACH__ 564 return (snprintf(buf, len, "lib%s.%s%sdylib", base, 565 version ? version : "", version ? "." : "")); 566 #else 567 return (snprintf(buf, len, "lib%s.so%s%s", base, 568 version ? "." : "", version ? version : "")); 569 #endif 570 } 571 572 /* 573 * This is really gross: we need to free up JS function slots when then list 574 * is freed, but there's no way for us to know that's happening. So we 575 * interpose on nvlist_free() here, checking for function slots to free iff 576 * this is a list that has a V8 JS function handle in it. Lists created by 577 * someone else, even if they have uint64 arrays in them, are passed through. 578 * This whole thing makes me want to cry. Why can't we just have a decent 579 * JS VM?! 580 */ 581 extern "C" void 582 nvlist_free(nvlist_t *lp) 583 { 584 uint64_t *vp; 585 uint_t nv; 586 nvpair_t *pp = NULL; 587 588 if (lp == NULL) 589 return; 590 591 if (__real_nvlist_free == NULL) { 592 char *libname; 593 size_t len; 594 void *dlhdl; 595 596 len = library_name("nvpair", "1", NULL, 0) + 1; 597 libname = reinterpret_cast<char *>(alloca(len)); 598 (void) library_name("nvpair", "1", libname, len); 599 600 dlhdl = dlopen(libname, RTLD_LAZY | RTLD_LOCAL); 601 if (dlhdl == NULL) { 602 v8plus_panic("unable to dlopen libnvpair: %s", 603 dlerror()); 604 } 605 __real_nvlist_free = (void (*)(nvlist_t *)) 606 dlsym(dlhdl, "nvlist_free"); 607 if (__real_nvlist_free == NULL) 608 v8plus_panic("unable to find nvlist_free"); 609 } 610 611 if (nvlist_exists(lp, V8PLUS_JSF_COOKIE)) { 612 while ((pp = nvlist_next_nvpair(lp, pp)) != NULL) { 613 if (nvpair_type(pp) != DATA_TYPE_UINT64_ARRAY) 614 continue; 615 if (nvpair_value_uint64_array(pp, &vp, &nv) != 0) { 616 v8plus_panic( 617 "unable to obtain callbach hash tag"); 618 } 619 if (nv != 1) { 620 v8plus_panic( 621 "bad array size %u for callback hash tag", 622 nv); 623 } 624 v8plus_jsfunc_rele(*vp); 625 } 626 } 627 628 __real_nvlist_free(lp); 629 } 630 631 extern "C" int 632 nvpair_value_v8plus_jsfunc(const nvpair_t *pp, v8plus_jsfunc_t *vp) 633 { 634 uint64_t *lvp; 635 uint_t nv; 636 int err; 637 638 if ((err = nvpair_value_uint64_array((nvpair_t *)pp, &lvp, &nv)) != 0) 639 return (err); 640 641 *vp = *lvp; 642 643 return (0); 644 } 645 646 extern "C" void 647 v8plus_obj_hold(const void *cop) 648 { 649 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop); 650 op->public_Ref(); 651 652 /* 653 * If the consumer puts a hold on an object, we should also put a hold 654 * on the V8 event loop to prevent it dematerialising beneath us. 655 */ 656 v8plus_eventloop_hold(); 657 } 658 659 extern "C" void 660 v8plus_obj_rele_direct(const void *cop) 661 { 662 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop); 663 op->public_Unref(); 664 665 /* 666 * Release the event loop hold we took in v8plus_obj_hold(): 667 */ 668 v8plus_eventloop_rele_direct(); 669 }