v8embed.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "platform.h"
  14. #include "v8.h"
  15. #include "jexcept.hpp"
  16. #include "jthread.hpp"
  17. #include "hqlplugins.hpp"
  18. #include "deftype.hpp"
  19. #include "eclrtl.hpp"
  20. #include "eclrtl_imp.hpp"
  21. #ifdef _WIN32
  22. #define EXPORT __declspec(dllexport)
  23. #else
  24. #define EXPORT
  25. #endif
  26. static const char * compatibleVersions[] = {
  27. "V8 JavaScript Embed Helper 1.0.0",
  28. NULL };
  29. static const char *version = "V8 JavaScript Embed Helper 1.0.0";
  30. extern "C" EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
  31. {
  32. if (pb->size == sizeof(ECLPluginDefinitionBlockEx))
  33. {
  34. ECLPluginDefinitionBlockEx * pbx = (ECLPluginDefinitionBlockEx *) pb;
  35. pbx->compatibleVersions = compatibleVersions;
  36. }
  37. else if (pb->size != sizeof(ECLPluginDefinitionBlock))
  38. return false;
  39. pb->magicVersion = PLUGIN_VERSION;
  40. pb->version = version;
  41. pb->moduleName = "javascript";
  42. pb->ECL = NULL;
  43. pb->flags = PLUGIN_MULTIPLE_VERSIONS;
  44. pb->description = "V8 JavaScript Embed Helper";
  45. return true;
  46. }
  47. namespace javascriptLanguageHelper {
  48. class V8JavascriptEmbedFunctionContext : public CInterfaceOf<IEmbedFunctionContext>
  49. {
  50. public:
  51. V8JavascriptEmbedFunctionContext()
  52. {
  53. isolate = v8::Isolate::New();
  54. isolate->Enter();
  55. context = v8::Context::New();
  56. context->Enter();
  57. }
  58. ~V8JavascriptEmbedFunctionContext()
  59. {
  60. script.Dispose();
  61. result.Dispose();
  62. context->Exit();
  63. context.Dispose();
  64. isolate->Exit();
  65. isolate->Dispose();
  66. }
  67. virtual void bindBooleanParam(const char *name, bool val)
  68. {
  69. v8::HandleScope handle_scope;
  70. context->Global()->Set(v8::String::New(name), v8::Boolean::New(val));
  71. }
  72. virtual void bindDataParam(const char *name, size32_t len, const void *val)
  73. {
  74. v8::HandleScope handle_scope;
  75. v8::Local<v8::Array> array = v8::Array::New(len);
  76. const byte *vval = (const byte *) val;
  77. for (int i = 0; i < len; i++)
  78. {
  79. array->Set(v8::Number::New(i), v8::Integer::New(vval[i])); // feels horridly inefficient, but seems to be the expected approach
  80. }
  81. context->Global()->Set(v8::String::New(name), array);
  82. }
  83. virtual void bindRealParam(const char *name, double val)
  84. {
  85. v8::HandleScope handle_scope;
  86. context->Global()->Set(v8::String::New(name), v8::Number::New(val));
  87. }
  88. virtual void bindSignedParam(const char *name, __int64 val)
  89. {
  90. // MORE - might need to check does not overflow 32 bits? Or store as a real?
  91. v8::HandleScope handle_scope;
  92. context->Global()->Set(v8::String::New(name), v8::Integer::New(val));
  93. }
  94. virtual void bindUnsignedParam(const char *name, unsigned __int64 val)
  95. {
  96. // MORE - might need to check does not overflow 32 bits
  97. v8::HandleScope handle_scope;
  98. context->Global()->Set(v8::String::New(name), v8::Integer::NewFromUnsigned(val));
  99. }
  100. virtual void bindStringParam(const char *name, size32_t len, const char *val)
  101. {
  102. size32_t utfCharCount;
  103. rtlDataAttr utfText;
  104. rtlStrToUtf8X(utfCharCount, utfText.refstr(), len, val);
  105. bindUTF8Param(name, utfCharCount, utfText.getstr());
  106. }
  107. virtual void bindVStringParam(const char *name, const char *val)
  108. {
  109. bindStringParam(name, strlen(val), val);
  110. }
  111. virtual void bindUTF8Param(const char *name, size32_t chars, const char *val)
  112. {
  113. v8::HandleScope handle_scope;
  114. context->Global()->Set(v8::String::New(name), v8::String::New(val, rtlUtf8Size(chars, val)));
  115. }
  116. virtual void bindUnicodeParam(const char *name, size32_t chars, const UChar *val)
  117. {
  118. v8::HandleScope handle_scope;
  119. context->Global()->Set(v8::String::New(name), v8::String::New(val, chars));
  120. }
  121. virtual void bindSetParam(const char *name, int elemType, size32_t elemSize, bool isAll, size32_t totalBytes, void *setData)
  122. {
  123. if (isAll)
  124. rtlFail(0, "v8embed: Cannot pass ALL");
  125. v8::HandleScope handle_scope;
  126. type_t typecode = (type_t) elemType;
  127. const byte *inData = (const byte *) setData;
  128. const byte *endData = inData + totalBytes;
  129. int numElems;
  130. if (elemSize == UNKNOWN_LENGTH)
  131. {
  132. numElems = 0;
  133. // Will need 2 passes to work out how many elements there are in the set :(
  134. while (inData < endData)
  135. {
  136. int thisSize;
  137. switch (elemType)
  138. {
  139. case type_varstring:
  140. thisSize = strlen((const char *) inData) + 1;
  141. break;
  142. case type_string:
  143. thisSize = * (size32_t *) inData + sizeof(size32_t);
  144. break;
  145. case type_unicode:
  146. thisSize = (* (size32_t *) inData) * sizeof(UChar) + sizeof(size32_t);
  147. break;
  148. case type_utf8:
  149. thisSize = rtlUtf8Size(* (size32_t *) inData, inData + sizeof(size32_t)) + sizeof(size32_t);
  150. break;
  151. default:
  152. rtlFail(0, "v8embed: Unsupported parameter type");
  153. break;
  154. }
  155. inData += thisSize;
  156. numElems++;
  157. }
  158. inData = (const byte *) setData;
  159. }
  160. else
  161. numElems = totalBytes / elemSize;
  162. v8::Local<v8::Array> array = v8::Array::New(numElems);
  163. v8::Handle<v8::Value> thisItem;
  164. size32_t thisSize = elemSize;
  165. for (int idx = 0; idx < numElems; idx++)
  166. {
  167. switch (typecode)
  168. {
  169. case type_int:
  170. thisItem = v8::Integer::New(rtlReadInt(inData, elemSize));
  171. break;
  172. case type_unsigned:
  173. thisItem = v8::Integer::NewFromUnsigned(rtlReadUInt(inData, elemSize));
  174. break;
  175. case type_varstring:
  176. {
  177. size32_t numChars = strlen((const char *) inData);
  178. size32_t utfCharCount;
  179. rtlDataAttr utfText;
  180. rtlStrToUtf8X(utfCharCount, utfText.refstr(), numChars, (const char *) inData);
  181. thisItem = v8::String::New(utfText.getstr(), rtlUtf8Size(utfCharCount, utfText.getstr()));
  182. if (elemSize == UNKNOWN_LENGTH)
  183. thisSize = numChars + 1;
  184. break;
  185. }
  186. case type_string:
  187. {
  188. if (elemSize == UNKNOWN_LENGTH)
  189. {
  190. thisSize = * (size32_t *) inData;
  191. inData += sizeof(size32_t);
  192. }
  193. size32_t utfCharCount;
  194. rtlDataAttr utfText;
  195. rtlStrToUtf8X(utfCharCount, utfText.refstr(), thisSize, (const char *) inData);
  196. thisItem = v8::String::New(utfText.getstr(), rtlUtf8Size(utfCharCount, utfText.getstr()));
  197. break;
  198. }
  199. case type_real:
  200. if (elemSize == sizeof(double))
  201. thisItem = v8::Number::New(* (double *) inData);
  202. else
  203. thisItem = v8::Number::New(* (float *) inData);
  204. break;
  205. case type_boolean:
  206. assertex(elemSize == sizeof(bool));
  207. thisItem = v8::Boolean::New(* (bool *) inData);
  208. break;
  209. case type_unicode:
  210. {
  211. if (elemSize == UNKNOWN_LENGTH)
  212. {
  213. thisSize = (* (size32_t *) inData) * sizeof(UChar); // NOTE - it's in chars...
  214. inData += sizeof(size32_t);
  215. }
  216. thisItem = v8::String::New((const UChar *) inData, thisSize/sizeof(UChar));
  217. break;
  218. }
  219. case type_utf8:
  220. {
  221. assertex (elemSize == UNKNOWN_LENGTH);
  222. size32_t numChars = * (size32_t *) inData;
  223. inData += sizeof(size32_t);
  224. thisSize = rtlUtf8Size(numChars, inData);
  225. thisItem = v8::String::New((const char *) inData, thisSize);
  226. break;
  227. }
  228. default:
  229. rtlFail(0, "v8embed: Unsupported parameter type");
  230. break;
  231. }
  232. inData += thisSize;
  233. array->Set(v8::Number::New(idx), thisItem);
  234. }
  235. context->Global()->Set(v8::String::New(name), array);
  236. }
  237. virtual bool getBooleanResult()
  238. {
  239. assertex (!result.IsEmpty());
  240. v8::HandleScope handle_scope;
  241. return result->BooleanValue();
  242. }
  243. virtual void getDataResult(size32_t &__len, void * &__result)
  244. {
  245. assertex (!result.IsEmpty() && result->IsArray());
  246. v8::HandleScope handle_scope;
  247. v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(result);
  248. __len = array->Length();
  249. __result = rtlMalloc(__len);
  250. byte *bresult = (byte *) __result;
  251. for (size32_t i = 0; i < __len; i++)
  252. {
  253. bresult[i] = v8::Integer::Cast(*array->Get(i))->Value(); // feels horridly inefficient, but seems to be the expected approach
  254. }
  255. }
  256. virtual double getRealResult()
  257. {
  258. assertex (!result.IsEmpty());
  259. v8::HandleScope handle_scope;
  260. return v8::Number::Cast(*result)->Value();
  261. }
  262. virtual __int64 getSignedResult()
  263. {
  264. assertex (!result.IsEmpty());
  265. v8::HandleScope handle_scope;
  266. return v8::Integer::Cast(*result)->Value();
  267. }
  268. virtual unsigned __int64 getUnsignedResult()
  269. {
  270. assertex (!result.IsEmpty());
  271. v8::HandleScope handle_scope;
  272. return v8::Integer::Cast(*result)->Value();
  273. }
  274. virtual void getStringResult(size32_t &__chars, char * &__result)
  275. {
  276. assertex (!result.IsEmpty() && result->IsString());
  277. v8::HandleScope handle_scope;
  278. v8::String::AsciiValue ascii(result);
  279. rtlStrToStrX(__chars, __result, ascii.length(), *ascii);
  280. }
  281. virtual void getUTF8Result(size32_t &__chars, char * &__result)
  282. {
  283. assertex (!result.IsEmpty() && result->IsString());
  284. v8::HandleScope handle_scope;
  285. v8::String::Utf8Value utf8(result);
  286. unsigned numchars = rtlUtf8Length(utf8.length(), *utf8);
  287. rtlUtf8ToUtf8X(__chars, __result, numchars, *utf8);
  288. }
  289. virtual void getUnicodeResult(size32_t &__chars, UChar * &__result)
  290. {
  291. assertex (!result.IsEmpty() && result->IsString());
  292. v8::HandleScope handle_scope;
  293. v8::String::Utf8Value utf8(result);
  294. unsigned numchars = rtlUtf8Length(utf8.length(), *utf8);
  295. rtlUtf8ToUnicodeX(__chars, __result, numchars, *utf8);
  296. }
  297. virtual void getSetResult(bool & __isAllResult, size32_t & __resultBytes, void * & __result, int elemType, size32_t elemSize)
  298. {
  299. assertex (!result.IsEmpty());
  300. if (!result->IsArray())
  301. rtlFail(0, "v8embed: type mismatch - return value was not an array");
  302. v8::HandleScope handle_scope;
  303. v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(result);
  304. size_t numResults = array->Length();
  305. rtlRowBuilder out;
  306. byte *outData = NULL;
  307. size32_t outBytes = 0;
  308. if (elemSize != UNKNOWN_LENGTH)
  309. {
  310. out.ensureAvailable(numResults * elemSize); // MORE - check for overflow?
  311. outData = out.getbytes();
  312. }
  313. for (int i = 0; i < numResults; i++)
  314. {
  315. v8::Local<v8::Value> elem = array->Get(i);
  316. if (elem.IsEmpty())
  317. rtlFail(0, "v8embed: type mismatch - empty value in returned array");
  318. switch ((type_t) elemType)
  319. {
  320. case type_int:
  321. rtlWriteInt(outData, v8::Integer::Cast(*elem)->Value(), elemSize);
  322. break;
  323. case type_unsigned:
  324. rtlWriteInt(outData, v8::Integer::Cast(*elem)->Value(), elemSize);
  325. break;
  326. case type_real:
  327. if (elemSize == sizeof(double))
  328. * (double *) outData = (double) v8::Number::Cast(*elem)->Value();
  329. else
  330. {
  331. assertex(elemSize == sizeof(float));
  332. * (float *) outData = (float) v8::Number::Cast(*elem)->Value();
  333. }
  334. break;
  335. case type_boolean:
  336. assertex(elemSize == sizeof(bool));
  337. * (bool *) outData = elem->BooleanValue();
  338. break;
  339. case type_string:
  340. case type_varstring:
  341. {
  342. if (!elem->IsString())
  343. rtlFail(0, "v8embed: type mismatch - return value in list was not a STRING");
  344. v8::String::AsciiValue ascii(elem);
  345. const char * text = *ascii;
  346. size_t lenBytes = ascii.length();
  347. if (elemSize == UNKNOWN_LENGTH)
  348. {
  349. if (elemType == type_string)
  350. {
  351. out.ensureAvailable(outBytes + lenBytes + sizeof(size32_t));
  352. outData = out.getbytes() + outBytes;
  353. * (size32_t *) outData = lenBytes;
  354. rtlStrToStr(lenBytes, outData+sizeof(size32_t), lenBytes, text);
  355. outBytes += lenBytes + sizeof(size32_t);
  356. }
  357. else
  358. {
  359. out.ensureAvailable(outBytes + lenBytes + 1);
  360. outData = out.getbytes() + outBytes;
  361. rtlStrToVStr(0, outData, lenBytes, text);
  362. outBytes += lenBytes + 1;
  363. }
  364. }
  365. else
  366. {
  367. if (elemType == type_string)
  368. rtlStrToStr(elemSize, outData, lenBytes, text);
  369. else
  370. rtlStrToVStr(elemSize, outData, lenBytes, text); // Fixed size null terminated strings... weird.
  371. }
  372. break;
  373. }
  374. case type_unicode:
  375. case type_utf8:
  376. {
  377. if (!elem->IsString())
  378. rtlFail(0, "v8embed: type mismatch - return value in list was not a STRING");
  379. v8::String::Utf8Value utf8(elem);
  380. size_t lenBytes = utf8.length();
  381. const char * text = *utf8;
  382. size32_t numchars = rtlUtf8Length(lenBytes, text);
  383. if (elemType == type_utf8)
  384. {
  385. assertex (elemSize == UNKNOWN_LENGTH);
  386. out.ensureAvailable(outBytes + lenBytes + sizeof(size32_t));
  387. outData = out.getbytes() + outBytes;
  388. * (size32_t *) outData = numchars;
  389. rtlStrToStr(lenBytes, outData+sizeof(size32_t), lenBytes, text);
  390. outBytes += lenBytes + sizeof(size32_t);
  391. }
  392. else
  393. {
  394. if (elemSize == UNKNOWN_LENGTH)
  395. {
  396. out.ensureAvailable(outBytes + numchars*sizeof(UChar) + sizeof(size32_t));
  397. outData = out.getbytes() + outBytes;
  398. // You can't assume that number of chars in utf8 matches number in unicode16 ...
  399. size32_t numchars16;
  400. rtlDataAttr unicode16;
  401. rtlUtf8ToUnicodeX(numchars16, unicode16.refustr(), numchars, text);
  402. * (size32_t *) outData = numchars16;
  403. rtlUnicodeToUnicode(numchars16, (UChar *) (outData+sizeof(size32_t)), numchars16, unicode16.getustr());
  404. outBytes += numchars16*sizeof(UChar) + sizeof(size32_t);
  405. }
  406. else
  407. rtlUtf8ToUnicode(elemSize / sizeof(UChar), (UChar *) outData, numchars, text);
  408. }
  409. break;
  410. }
  411. default:
  412. rtlFail(0, "v8embed: type mismatch - unsupported return type");
  413. }
  414. if (elemSize != UNKNOWN_LENGTH)
  415. {
  416. outData += elemSize;
  417. outBytes += elemSize;
  418. }
  419. }
  420. __isAllResult = false;
  421. __resultBytes = outBytes;
  422. __result = out.detachdata();
  423. }
  424. virtual IRowStream *getDatasetResult(IEngineRowAllocator * _resultAllocator)
  425. {
  426. assertex (!result.IsEmpty());
  427. if (!result->IsArray())
  428. rtlFail(0, "v8embed: type mismatch - return value was not an array");
  429. UNIMPLEMENTED;
  430. // resultIterator.setown(new ArrayIterator(result);
  431. // resultAllocator.set(_resultAllocator);
  432. // return LINK(this);
  433. }
  434. virtual byte * getRowResult(IEngineRowAllocator * _resultAllocator)
  435. {
  436. UNIMPLEMENTED;
  437. }
  438. virtual size32_t getTransformResult(ARowBuilder & builder)
  439. {
  440. UNIMPLEMENTED;
  441. }
  442. virtual const void *nextRow()
  443. {
  444. // assertex(resultAllocator);
  445. // assertex(resultIterator);
  446. UNIMPLEMENTED;
  447. }
  448. virtual void compileEmbeddedScript(size32_t lenChars, const char *utf)
  449. {
  450. v8::HandleScope handle_scope;
  451. v8::Handle<v8::String> source = v8::String::New(utf, rtlUtf8Size(lenChars, utf));
  452. v8::Handle<v8::Script> lscript = v8::Script::Compile(source);
  453. script = v8::Persistent<v8::Script>::New(lscript);
  454. }
  455. virtual void importFunction(size32_t lenChars, const char *utf)
  456. {
  457. UNIMPLEMENTED; // Not sure if meaningful for js
  458. }
  459. virtual void callFunction()
  460. {
  461. assertex (!script.IsEmpty());
  462. v8::HandleScope handle_scope;
  463. v8::TryCatch tryCatch;
  464. result = v8::Persistent<v8::Value>::New(script->Run());
  465. v8::Handle<v8::Value> exception = tryCatch.Exception();
  466. if (!exception.IsEmpty())
  467. {
  468. v8::String::AsciiValue msg(exception);
  469. throw MakeStringException(MSGAUD_user, 0, "v8embed: %s", *msg);
  470. }
  471. }
  472. protected:
  473. v8::Isolate *isolate;
  474. v8::Persistent<v8::Context> context;
  475. v8::Persistent<v8::Script> script;
  476. v8::Persistent<v8::Value> result;
  477. };
  478. static __thread V8JavascriptEmbedFunctionContext * theFunctionContext; // We reuse per thread, for speed
  479. static __thread ThreadTermFunc threadHookChain;
  480. static void releaseContext()
  481. {
  482. if (theFunctionContext)
  483. {
  484. ::Release(theFunctionContext);
  485. theFunctionContext = NULL;
  486. }
  487. if (threadHookChain)
  488. {
  489. (*threadHookChain)();
  490. threadHookChain = NULL;
  491. }
  492. }
  493. class V8JavascriptEmbedContext : public CInterfaceOf<IEmbedContext>
  494. {
  495. public:
  496. V8JavascriptEmbedContext()
  497. {
  498. }
  499. virtual IEmbedFunctionContext *createFunctionContext(bool isImport, const char *options)
  500. {
  501. assertex(!isImport);
  502. if (!theFunctionContext)
  503. {
  504. theFunctionContext = new V8JavascriptEmbedFunctionContext;
  505. threadHookChain = addThreadTermFunc(releaseContext);
  506. }
  507. return LINK(theFunctionContext);
  508. }
  509. } theEmbedContext;
  510. extern IEmbedContext* getEmbedContext()
  511. {
  512. return LINK(&theEmbedContext);
  513. }
  514. extern bool syntaxCheck(const char *script)
  515. {
  516. return true; // MORE
  517. }
  518. } // namespace