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(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 extern "C" void
529 v8plus_jsfunc_rele(v8plus_jsfunc_t f)
530 {
531 v8::Local<v8::Function> lfh;
532 std::unordered_map<uint64_t, cb_hdl_t>::iterator it;
533
534 if ((it = cbhash.find(f)) == cbhash.end())
535 v8plus_panic("callback hash tag %llu not found", f);
536
537 if (it->second.ch_refs == 0)
538 v8plus_panic("releasing unheld callback hash tag %llu", f);
539
540 if (--it->second.ch_refs == 0) {
541 if (it->second.ch_persist) {
542 v8::Persistent<v8::Function> pfh(it->second.ch_hdl);
543 pfh.Dispose();
544 }
545 cbhash.erase(it);
546 }
547 }
548
549 static size_t
550 library_name(const char *base, const char *version, char *buf, size_t len)
551 {
552 #ifdef __MACH__
553 return (snprintf(buf, len, "lib%s.%s%sdylib", base,
554 version ? version : "", version ? "." : ""));
555 #else
556 return (snprintf(buf, len, "lib%s.so%s%s", base,
557 version ? "." : "", version ? version : ""));
558 #endif
559 }
560
561 /*
562 * This is really gross: we need to free up JS function slots when then list
563 * is freed, but there's no way for us to know that's happening. So we
564 * interpose on nvlist_free() here, checking for function slots to free iff
565 * this is a list that has a V8 JS function handle in it. Lists created by
566 * someone else, even if they have uint64 arrays in them, are passed through.
567 * This whole thing makes me want to cry. Why can't we just have a decent
568 * JS VM?!
569 */
570 extern "C" void
571 nvlist_free(nvlist_t *lp)
572 {
573 uint64_t *vp;
574 uint_t nv;
575 nvpair_t *pp = NULL;
576
577 if (lp == NULL)
578 return;
579
580 if (__real_nvlist_free == NULL) {
581 char *libname;
582 size_t len;
583 void *dlhdl;
584
585 len = library_name("nvpair", "1", NULL, 0) + 1;
586 libname = reinterpret_cast<char *>(alloca(len));
587 (void) library_name("nvpair", "1", libname, len);
588
589 dlhdl = dlopen(libname, RTLD_LAZY | RTLD_LOCAL);
590 if (dlhdl == NULL) {
591 v8plus_panic("unable to dlopen libnvpair: %s",
592 dlerror());
593 }
594 __real_nvlist_free = (void (*)(nvlist_t *))
595 dlsym(dlhdl, "nvlist_free");
596 if (__real_nvlist_free == NULL)
597 v8plus_panic("unable to find nvlist_free");
598 }
599
600 if (nvlist_exists(lp, V8PLUS_JSF_COOKIE)) {
601 while ((pp = nvlist_next_nvpair(lp, pp)) != NULL) {
602 if (nvpair_type(pp) != DATA_TYPE_UINT64_ARRAY)
603 continue;
604 if (nvpair_value_uint64_array(pp, &vp, &nv) != 0) {
605 v8plus_panic(
606 "unable to obtain callbach hash tag");
607 }
608 if (nv != 1) {
609 v8plus_panic(
610 "bad array size %u for callback hash tag",
611 nv);
612 }
613 v8plus_jsfunc_rele(*vp);
614 }
615 }
616
617 __real_nvlist_free(lp);
618 }
619
620 extern "C" int
621 nvpair_value_v8plus_jsfunc(const nvpair_t *pp, v8plus_jsfunc_t *vp)
622 {
623 uint64_t *lvp;
624 uint_t nv;
625 int err;
626
627 if ((err = nvpair_value_uint64_array((nvpair_t *)pp, &lvp, &nv)) != 0)
628 return (err);
629
630 *vp = *lvp;
631
632 return (0);
633 }
634
635 extern "C" void
636 v8plus_obj_hold(const void *cop)
637 {
638 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop);
639 op->public_Ref();
640 }
641
642 extern "C" void
643 v8plus_obj_rele(const void *cop)
644 {
645 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop);
646 op->public_Unref();
647 }