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(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 argc = max_argc;
466 nvlist_to_v8_argv(lp, &argc, argv);
467
468 if ((err = nvlist_alloc(&rp, NV_UNIQUE_NAME, 0)) != 0)
469 return (v8plus_nverr(err, NULL));
470
471 v8::TryCatch tc;
472 res = op->call(name, argc, argv);
473 if (tc.HasCaught()) {
474 err = nvlist_add_v8_Value(rp, "err", tc.Exception());
475 tc.Reset();
476 if (err != 0) {
477 nvlist_free(rp);
478 return (v8plus_nverr(err, "err"));
479 }
480 } else if ((err = nvlist_add_v8_Value(rp, "res", res)) != 0) {
481 nvlist_free(rp);
482 return (v8plus_nverr(err, "res"));
483 }
484
485 return (rp);
486 }
487
488 extern "C" int
489 nvlist_lookup_v8plus_jsfunc(const nvlist_t *lp, const char *name,
490 v8plus_jsfunc_t *vp)
491 {
492 uint64_t *lvp;
493 uint_t nv;
494 int err;
495
496 err = nvlist_lookup_uint64_array(const_cast<nvlist_t *>(lp),
497 name, &lvp, &nv);
498 if (err != 0)
499 return (err);
500
501 if (nv != 1)
502 v8plus_panic("bad array size %u for callback hash tag", nv);
503
504 *vp = *lvp;
505 return (0);
506 }
507
508 extern "C" void
509 v8plus_jsfunc_hold(v8plus_jsfunc_t f)
510 {
511 v8::Persistent<v8::Function> pfh;
512 std::unordered_map<uint64_t, cb_hdl_t>::iterator it;
513
514 if ((it = cbhash.find(f)) == cbhash.end())
515 v8plus_panic("callback hash tag %llu not found", f);
516
517 if (!it->second.ch_persist) {
518 pfh = v8::Persistent<v8::Function>::New(it->second.ch_hdl);
519 it->second.ch_hdl = pfh;
520 it->second.ch_persist = _B_TRUE;
521 }
522 ++it->second.ch_refs;
523 }
524
525 extern "C" void
526 v8plus_jsfunc_rele(v8plus_jsfunc_t f)
527 {
528 v8::Local<v8::Function> lfh;
529 std::unordered_map<uint64_t, cb_hdl_t>::iterator it;
530
531 if ((it = cbhash.find(f)) == cbhash.end())
532 v8plus_panic("callback hash tag %llu not found", f);
533
534 if (it->second.ch_refs == 0)
535 v8plus_panic("releasing unheld callback hash tag %llu", f);
536
537 if (--it->second.ch_refs == 0) {
538 if (it->second.ch_persist) {
539 v8::Persistent<v8::Function> pfh(it->second.ch_hdl);
540 pfh.Dispose();
541 }
542 cbhash.erase(it);
543 }
544 }
545
546 static size_t
547 library_name(const char *base, const char *version, char *buf, size_t len)
548 {
549 #ifdef __MACH__
550 return (snprintf(buf, len, "lib%s.%s%sdylib", base,
551 version ? version : "", version ? "." : ""));
552 #else
553 return (snprintf(buf, len, "lib%s.so%s%s", base,
554 version ? "." : "", version ? version : ""));
555 #endif
556 }
557
558 /*
559 * This is really gross: we need to free up JS function slots when then list
560 * is freed, but there's no way for us to know that's happening. So we
561 * interpose on nvlist_free() here, checking for function slots to free iff
562 * this is a list that has a V8 JS function handle in it. Lists created by
563 * someone else, even if they have uint64 arrays in them, are passed through.
564 * This whole thing makes me want to cry. Why can't we just have a decent
565 * JS VM?!
566 */
567 extern "C" void
568 nvlist_free(nvlist_t *lp)
569 {
570 uint64_t *vp;
571 uint_t nv;
572 nvpair_t *pp = NULL;
573
574 if (lp == NULL)
575 return;
576
577 if (__real_nvlist_free == NULL) {
578 char *libname;
579 size_t len;
580 void *dlhdl;
581
582 len = library_name("nvpair", "1", NULL, 0) + 1;
583 libname = reinterpret_cast<char *>(alloca(len));
584 (void) library_name("nvpair", "1", libname, len);
585
586 dlhdl = dlopen(libname, RTLD_LAZY | RTLD_LOCAL);
587 if (dlhdl == NULL) {
588 v8plus_panic("unable to dlopen libnvpair: %s",
589 dlerror());
590 }
591 __real_nvlist_free = (void (*)(nvlist_t *))
592 dlsym(dlhdl, "nvlist_free");
593 if (__real_nvlist_free == NULL)
594 v8plus_panic("unable to find nvlist_free");
595 }
596
597 if (nvlist_exists(lp, V8PLUS_JSF_COOKIE)) {
598 while ((pp = nvlist_next_nvpair(lp, pp)) != NULL) {
599 if (nvpair_type(pp) != DATA_TYPE_UINT64_ARRAY)
600 continue;
601 if (nvpair_value_uint64_array(pp, &vp, &nv) != 0) {
602 v8plus_panic(
603 "unable to obtain callbach hash tag");
604 }
605 if (nv != 1) {
606 v8plus_panic(
607 "bad array size %u for callback hash tag",
608 nv);
609 }
610 v8plus_jsfunc_rele(*vp);
611 }
612 }
613
614 __real_nvlist_free(lp);
615 }
616
617 extern "C" int
618 nvpair_value_v8plus_jsfunc(const nvpair_t *pp, v8plus_jsfunc_t *vp)
619 {
620 uint64_t *lvp;
621 uint_t nv;
622 int err;
623
624 if ((err = nvpair_value_uint64_array((nvpair_t *)pp, &lvp, &nv)) != 0)
625 return (err);
626
627 *vp = *lvp;
628
629 return (0);
630 }
631
632 extern "C" void
633 v8plus_obj_hold(const void *cop)
634 {
635 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop);
636 op->public_Ref();
637 }
638
639 extern "C" void
640 v8plus_obj_rele(const void *cop)
641 {
642 v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop);
643 op->public_Unref();
644 }